Setup

library(data.table)
library(DBI)
library(ggplot2)
library(cowplot)
library(grid)
library(lme4)
library(lmerTest)

Sys.setlocale("LC_TIME", "en_US.UTF-8") # Print English date format
[1] "en_US.UTF-8"
en_US.UTF-8
# Sys.setlocale("LC_TIME", "nl_NL.UTF-8") # Print Dutch date format

number_format <- scales::number_format(big.mark = ",", decimal.mark = ".") # Print English number format
# number_format <- scales::number_format(big.mark = ".", decimal.mark = ",") # Print Dutch number format

theme_paper <- theme_classic(base_size = 12) + 
  theme(axis.text = element_text(colour = "black"),
        panel.grid.major.y = element_line(colour = "grey92"))

School closure and opening dates

Sources: - https://www.rijksoverheid.nl/actueel/nieuws/2020/03/15/aanvullende-maatregelen-onderwijs-horeca-sport - https://www.rijksoverheid.nl/actueel/nieuws/2020/05/19/onderwijs-gaat-stap-voor-stap-open

date_schools_closed <- as.POSIXct("2020-03-16")
date_schools_opened <- as.POSIXct("2020-06-02")

Handle database connections

db_connect <- function() {
  db <- dbConnect(RSQLite::SQLite(), file.path("..", "data", "noordhoff.sqlite"))
  return(db)
}

db_disconnect <- function(db) {
  dbDisconnect(db)
}

Data

The database contains all SlimStampen data collected via Noordhoff’s platform in three courses: Stepping Stones (English), Grandes Lignes (French), and Neue Kontakte (German).

Trial-level response data are stored in the responses table. Book information, such as the course year, book title, and chapter, are stored in the book_info table.

responses

Column Type Explanation
date int UNIX time stamp [s]
user_id chr unique user identifier
method chr course
start_time int elapsed time since session start [ms]
rt int response time [ms]
duration int trial duration [ms]
fact_id int unique fact identifier (within chapter)
correct int response accuracy
answer chr user’s response
choices int number of answer choices (1 == open response)
backspace_used dbl user pressed backspace during trial
backspace_used_first dbl user erased first character of response
study int trial was a study trial
answer_language chr language of the answer
subsession int identifies part within learning session
book_info_id chr unique identifier of book information

book_info

Column Type Explanation
book_info_id chr unique identifier of book information
method_group chr year and edition
book_title chr book title (incl. year, level, edition)
book_type chr type of book
chapter chr chapter number and title

Preview first 10 rows

db <- db_connect()
responses_top <- dbGetQuery(db, "SELECT * FROM responses LIMIT 10")
responses_top
book_info_top <- dbGetQuery(db, "SELECT * FROM book_info LIMIT 10")
book_info_top
db_disconnect(db)

Performance

There are several measures of learning performance we can look at. The most straight-forward of these are response accuracy and response time.

Important factors to keep in mind: question type (multiple choice or open answer) and language. Note that we cannot distinguish between NL-X and X-X, since we only know the language of the answer.

Response accuracy

Whole population

db <- db_connect()
correct <- dbGetQuery(db, 
                      "SELECT r.method AS 'method',
                      DATE(r.date + 3600, 'unixepoch') AS 'doy',
                      r.user_id AS 'user',
                      r.choices > 1 AS 'mcq',
                      r.correct AS 'correct',
                      COUNT(*) AS 'n'
                      FROM 'responses' r
                      WHERE r.study == 0
                      GROUP BY r.method,
                      DATE(r.date + 3600, 'unixepoch'),
                      r.user_id,
                      r.choices > 1,
                      r.correct"
)
setDT(correct)
db_disconnect(db)

Fill in missing rows (where all trials on a day were correct/incorrect):

correct <- tidyr::complete(correct, tidyr::nesting(method, doy, user, mcq), correct, fill = list(n = 0))
setDT(correct)
correct[, mcq := as.logical(mcq)]
accuracy <- correct[, .(accuracy = n[correct == 1]/sum(n), n = sum(n)), by = .(method, doy, user, mcq)]

Add a school year column (cutoff date: 1 August):

accuracy[, doy_posix := as.POSIXct(doy)]
accuracy[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add sensible course names:

accuracy[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Align school years:

accuracy[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
accuracy[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

accuracy[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
accuracy[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
accuracy_by_week_and_user <- accuracy[, .(accuracy = sum(accuracy*n)/sum(n)), by = .(course, school_year, doy_posix_aligned_week, user, mcq)]
accuracy_by_week <- accuracy_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
                              accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, mcq)]
# accuracy_by_week_and_user <- accuracy[, .(accuracy = mean(accuracy, na.rm = TRUE)), by = .(course, school_year, doy_posix_aligned_week, user, mcq)]
# accuracy_by_week <- accuracy_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
#                               accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, mcq)]

Add question type labels:

accuracy_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]

Plot response accuracy by week (mean +/- 1 SE).

p_acc <- ggplot(accuracy_by_week[(course == "English" & mcq == TRUE) | course == "French",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(. ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(limits = c(.7, 1), labels = scales::percent_format(accuracy = 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper
p_acc
Warning: Removed 24 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type.pdf", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type.png", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).

By level and year

db <- db_connect()
correct_strat <- dbGetQuery(db, 
                      "SELECT r.method AS 'method',
                      r.book_info_id AS 'book_info_id',
                      DATE(r.date + 3600, 'unixepoch') AS 'doy',
                      r.user_id AS 'user',
                      r.choices > 1 AS 'mcq',
                      r.correct AS 'correct',
                      COUNT(*) AS 'n'
                      FROM 'responses' r
                      WHERE r.study == 0
                      GROUP BY r.method,
                      r.book_info_id,
                      DATE(r.date + 3600, 'unixepoch'),
                      r.user_id,
                      r.choices > 1,
                      r.correct"
)
setDT(correct_strat)
db_disconnect(db)

Fill in missing rows (where all trials on a day were correct/incorrect):

correct_strat <- tidyr::complete(correct_strat, tidyr::nesting(method, book_info_id, doy, user, mcq), correct, fill = list(n = 0))
setDT(correct_strat)
correct_strat[, mcq := as.logical(mcq)]

Add book information:

db <- db_connect()
book_info <- dbGetQuery(db, "SELECT DISTINCT * FROM 'book_info'")
db_disconnect(db)

setDT(book_info)
correct_strat[book_info[book_type == "Hoofdboek",], on  = "book_info_id", c("method_group", "book_title") := .(i.method_group, i.book_title)]

Add sensible course names:

correct_strat[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Add a school year column (cutoff date: 1 August):

correct_strat[, doy_posix := as.POSIXct(doy)]
correct_strat[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Simplify level names:

# Keep all distinctions
correct_strat[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
correct_strat[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
correct_strat[, level := dplyr::case_when(
  grepl( "hv", book_title) ~ "General secondary\n(havo)",
  grepl("vmbo", book_title) ~ "Pre-vocational\n(vmbo)",
  grepl("havo", book_title) ~ "General secondary\n(havo)",
  grepl("vwo", book_title) ~ "Pre-university\n(vwo)",
  TRUE ~ "Other")]
correct_strat[, level := factor(level, levels = c("Other", "Pre-vocational\n(vmbo)", "General secondary\n(havo)", "Pre-university\n(vwo)"))]

Simplify year names:

correct_strat[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]

Consolidate by day:

accuracy_strat <- correct_strat[, .(accuracy = n[correct == 1]/sum(n), n = sum(n)), by = .(school_year, doy_posix, course, level, year, user, mcq)]

Align school years:

accuracy_strat[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
accuracy_strat[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

accuracy_strat[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
accuracy_strat[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
accuracy_strat_by_week_and_user <- accuracy_strat[, .(accuracy = sum(accuracy*n)/sum(n)), by = .(course, school_year, doy_posix_aligned_week, level, year, user, mcq)]
accuracy_strat_by_week <- accuracy_strat_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
                              accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, level, year, mcq)]

Add question type labels:

accuracy_strat_by_week_and_user[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
accuracy_strat_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]

How many unique users per group?

accuracy_strat_by_week_and_user[, .(unique_users = length(unique(user))),  by = .(course, level, year, school_year, question_type)]

Plot response accuracy by week (mean +/- 1 SE).

p_acc_level_year <- ggplot(accuracy_strat_by_week[course == "French" & level != "Other",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(.4, 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_acc_level_year
Warning: Removed 11 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type_french_level_year.pdf", width = 9, height = 5)
Warning: Removed 11 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type_french_level_year.png", width = 9, height = 5)
Warning: Removed 11 row(s) containing missing values (geom_path).
p_acc_level_year <- ggplot(accuracy_strat_by_week[course == "English" & level != "Other" & question_type == "Multiple\nchoice",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line() +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(.4, 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_acc_level_year
Warning: Removed 5 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type_english_level_year.pdf", width = 9, height = 5)
Warning: Removed 5 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type_english_level_year.png", width = 9, height = 5)
Warning: Removed 5 row(s) containing missing values (geom_path).

By level

accuracy_level_by_week_and_user <- accuracy_strat[, .(accuracy = sum(accuracy*n)/sum(n)), by = .(course, school_year, doy_posix_aligned_week, level, user, mcq)]

accuracy_level_by_week <- accuracy_level_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
                              accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, level, mcq)]

Add question type labels:

accuracy_level_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]

How many users in each group?

accuracy_level_by_week_and_user[, .(unique_users = length(unique(user))),  by = .(course, level, school_year, mcq)]

Plot response accuracy by week (mean +/- 1 SE).

p_acc_level <- ggplot(accuracy_level_by_week[((course == "English" & question_type == "Multiple\nchoice") | course == "French") & level != "Other",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(.6, 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_acc_level
Warning: Removed 20 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type_level.pdf", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type_level.png", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).

By year

accuracy_year_by_week_and_user <- accuracy_strat[, .(accuracy = sum(accuracy*n)/sum(n)), by = .(course, school_year, doy_posix_aligned_week, year, user, mcq)]

accuracy_year_by_week <- accuracy_year_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
                              accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, year, mcq)]

Add question type labels:

accuracy_year_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]

How many users in each group?

accuracy_year_by_week_and_user[, .(unique_users = length(unique(user))),  by = .(course, year, school_year, mcq)]

Plot response accuracy by week (mean +/- 1 SE).

p_acc_year <- ggplot(accuracy_year_by_week[((course == "English" & question_type == "Multiple\nchoice") | course == "French") & year != "Other",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(year ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(.6, 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_acc_year
Warning: Removed 14 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type_year.pdf", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type_year.png", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).

Regression model

Fit a mixed effects model to the daily accuracy data:

accuracy[, period := dplyr::case_when(
  doy_posix_aligned >= date_schools_opened ~ "post-lockdown",
  doy_posix_aligned >= date_schools_closed & doy_posix_aligned < date_schools_opened ~ "during-lockdown",
  doy_posix_aligned < date_schools_opened ~ "pre-lockdown"
)]
# Reorder factor levels so that intercept is pre-lockdown open answer in 19/20
accuracy[, period := factor(period, levels = c("pre-lockdown", "during-lockdown", "post-lockdown"))]
accuracy[, school_year := factor(school_year, levels = c("19/20", "18/19"))]
accuracy[, mcq := factor(mcq, levels = c(FALSE, TRUE))]

Since we know the number of trials per day and the proportion correct (accuracy), we can use a binomial GLMM:

if(!file.exists("../output/m_acc_fit.rds")) {
  m_acc <- glmer(accuracy ~ period*school_year*mcq + (1 | user) + (1 | course),
                 data = accuracy[(course == "English" & mcq == TRUE) | course == "French",],
                 family = "binomial", 
                 weights = n,
                 nAGQ = 0,
                 control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 1e6)))
  saveRDS(m_acc, "../output/m_acc_fit.rds")
} else {
  m_acc <- readRDS("../output/m_acc_fit.rds")
}

m_acc_summary <- summary(m_acc)
m_acc_summary
Generalized linear mixed model fit by maximum likelihood (Adaptive
  Gauss-Hermite Quadrature, nAGQ = 0) [glmerMod]
 Family: binomial  ( logit )
Formula: accuracy ~ period * school_year * mcq + (1 | user) + (1 | course)
   Data: 
accuracy[(course == "English" & mcq == TRUE) | course == "French",      ]
Weights: n
Control: 
glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 1e+06))

     AIC      BIC   logLik deviance df.resid 
 4963073  4963232 -2481522  4963045   671406 

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-23.9963  -0.9227   0.1606   1.0936  11.4371 

Random effects:
 Groups Name        Variance Std.Dev.
 user   (Intercept) 0.35034  0.5919  
 course (Intercept) 0.05511  0.2348  
Number of obs: 671420, groups:  user, 133419; course, 2

Fixed effects:
                                                Estimate Std. Error
(Intercept)                                     1.631257   0.166016
periodduring-lockdown                           0.259227   0.002725
periodpost-lockdown                             0.150384   0.004021
school_year18/19                                0.117385   0.002566
mcqTRUE                                         0.960249   0.002088
periodduring-lockdown:school_year18/19         -0.311338   0.004409
periodpost-lockdown:school_year18/19           -0.242816   0.006325
periodduring-lockdown:mcqTRUE                  -0.328352   0.002881
periodpost-lockdown:mcqTRUE                    -0.277485   0.004397
school_year18/19:mcqTRUE                        0.003357   0.002667
periodduring-lockdown:school_year18/19:mcqTRUE  0.379504   0.004758
periodpost-lockdown:school_year18/19:mcqTRUE    0.335859   0.006970
                                                z value Pr(>|z|)    
(Intercept)                                       9.826   <2e-16 ***
periodduring-lockdown                            95.142   <2e-16 ***
periodpost-lockdown                              37.399   <2e-16 ***
school_year18/19                                 45.755   <2e-16 ***
mcqTRUE                                         459.903   <2e-16 ***
periodduring-lockdown:school_year18/19          -70.613   <2e-16 ***
periodpost-lockdown:school_year18/19            -38.387   <2e-16 ***
periodduring-lockdown:mcqTRUE                  -113.954   <2e-16 ***
periodpost-lockdown:mcqTRUE                     -63.110   <2e-16 ***
school_year18/19:mcqTRUE                          1.259    0.208    
periodduring-lockdown:school_year18/19:mcqTRUE   79.765   <2e-16 ***
periodpost-lockdown:school_year18/19:mcqTRUE     48.183   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
                  (Intr) prddr- prdps- sc_18/19 mcTRUE prdd-:_18/19
prddrng-lck       -0.007                                           
prdpst-lckd       -0.005  0.346                                    
schl_y18/19       -0.008  0.452  0.297                             
mcqTRUE           -0.010  0.534  0.352  0.592                      
prdd-:_18/19       0.005 -0.626 -0.217 -0.505   -0.340             
prdp-:_18/19       0.003 -0.229 -0.640 -0.351   -0.233  0.241      
prddr-:TRUE        0.007 -0.824 -0.278 -0.412   -0.649  0.516      
prdps-:TRUE        0.004 -0.269 -0.824 -0.264   -0.419  0.169      
s_18/19:TRU        0.007 -0.409 -0.270 -0.837   -0.697  0.450      
prdd-:_18/19:TRUE -0.004  0.506  0.171  0.439    0.405 -0.854      
prdp-:_18/19:TRUE -0.003  0.176  0.523  0.298    0.273 -0.190      
                  prdp-:_18/19 prdd-:TRUE prdp-:TRUE s_18/19:
prddrng-lck                                                  
prdpst-lckd                                                  
schl_y18/19                                                  
mcqTRUE                                                      
prdd-:_18/19                                                 
prdp-:_18/19                                                 
prddr-:TRUE        0.183                                     
prdps-:TRUE        0.527        0.315                        
s_18/19:TRU        0.311        0.481      0.310             
prdd-:_18/19:TRUE -0.194       -0.612     -0.194     -0.523  
prdp-:_18/19:TRUE -0.843       -0.205     -0.634     -0.358  
                  prdd-:_18/19:TRUE
prddrng-lck                        
prdpst-lckd                        
schl_y18/19                        
mcqTRUE                            
prdd-:_18/19                       
prdp-:_18/19                       
prddr-:TRUE                        
prdps-:TRUE                        
s_18/19:TRU                        
prdd-:_18/19:TRUE                  
prdp-:_18/19:TRUE  0.221           

Save coefficients as a table for in the paper:

m_acc_coef <- as.data.frame(m_acc_summary$coefficients)
setDT(m_acc_coef, keep.rownames = TRUE)
m_acc_coef$rn <- c("Intercept \\small{(Period: pre-lockdown, School year: 19/20, Question type: open answer)}",
                   "Period: lockdown",
                   "Period: post-lockdown",
                   "School year: 18/19",
                   "Question type: multiple choice",
                   "Period: lockdown $\\times$ School year: 18/19",
                   "Period: post-lockdown $\\times$ School year: 18/19",
                   "Period: lockdown $\\times$ Question type: multiple choice",
                   "Period: post-lockdown $\\times$ Question type: multiple choice",
                   "School year: 18/19 $\\times$ Question type: multiple choice",
                   "Period: lockdown $\\times$ School year: 18/19 $\\times$ Question type: multiple choice",
                   "Period: post-lockdown $\\times$ School year: 18/19 $\\times$ Question type: multiple choice")

# Format p-values
m_acc_coef$`Pr(>|z|)` <- format.pval(m_acc_coef$`Pr(>|z|)`, eps = .001, digits = 3, flag = "0")
m_acc_coef$`Pr(>|z|)` <- sub('^(<)?0[.]', '\\1.', m_acc_coef$`Pr(>|z|)`) # Remove leading zero

cat(knitr::kable(m_acc_coef,
                 align = c("l","r", "r", "r", "r"),
                 digits = c(NA, 3, 3, 2, NA),
                 col.names = c("Effect", "$b$", "SE", "$z$", "$p$"),
                 format = "latex",
                 booktabs = TRUE,
                 escape = FALSE),
    file = "../output/m_acc_table.tex")

Visualise the model fit:

acc_fit <- expand.grid(period = c("pre-lockdown", "during-lockdown", "post-lockdown"), school_year = c("18/19", "19/20"), mcq = c(TRUE, FALSE))
acc_fit <- cbind(acc_fit, accuracy = predict(m_acc, type = "response", re.form = NA, newdata = acc_fit))
acc_fit
ggplot(acc_fit, aes(x = period, y = accuracy, colour = school_year, lty = mcq, group = interaction(mcq, school_year))) +
  geom_line() +
  geom_point() +
  scale_y_continuous(limits = c(.7, 1), labels = scales::percent_format()) +
  theme_paper

Empirical means:

accuracy_mean <- accuracy[(course == "English" & mcq == TRUE) | course == "French", .(accuracy = sum(accuracy * n)/sum(n)), by = .(period, school_year, mcq, user, course)][, .(accuracy = mean(accuracy), accuracy_sd = sd(accuracy)), by = .(period, school_year, mcq)]
accuracy_mean
ggplot(accuracy_mean, aes(x = period, y = accuracy, colour = school_year, lty = mcq, group = interaction(mcq, school_year))) +
  geom_line() +
  geom_point() +
  scale_y_continuous(limits = c(.7, 1), labels = scales::percent_format()) +
  theme_paper

Response time

db <- db_connect()
rt <- dbGetQuery(db, 
                 "SELECT r.method AS 'method',
                  r.book_info_id AS 'book_info_id',
                  DATE(r.date + 3600, 'unixepoch') AS 'doy',
                  r.user_id AS 'user',
                  r.choices > 1 AS 'mcq',
                  r.rt AS 'rt'
                  FROM 'responses' r
                  WHERE r.study == 0
                  AND r.correct == 1
                 "
)
setDT(rt)
db_disconnect(db)
doys <- rt[, .(doy = unique(doy))][, doy_posix := as.POSIXct(doy)][]
doys[, doy_posix_week := cut.POSIXt(as.POSIXct(doy), "week")]
doys[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]
doys[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
doys[school_year == "19/20", doy_posix_aligned := doy_posix]
doys[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
doys[, period := dplyr::case_when(
  doy_posix_aligned >= date_schools_opened ~ "post-lockdown",
  doy_posix_aligned >= date_schools_closed & doy_posix_aligned < date_schools_opened ~ "during-lockdown",
  doy_posix_aligned < date_schools_opened ~ "pre-lockdown"
)]
# Reorder factor levels so that intercept is pre-lockdown in 19/20
doys[, period := factor(period, levels = c("pre-lockdown", "during-lockdown", "post-lockdown"))]
doys[, school_year := factor(school_year, levels = c("19/20", "18/19"))]
rt <- rt[doys, on = "doy"]
rt[, mcq := as.factor(as.logical(mcq))]
rt[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Throw out trials with negative RTs (timing errors)

rt <- rt[rt > 0]

Whole population

rt_med <- rt[, .(rt_median = median(rt)), by = .(school_year, mcq, user, course, doy_posix_week)]

rt_by_week <- rt_med[, .(rt = mean(rt_median), rt_se = sd(rt_median)/sqrt(.N)), by = .(school_year, mcq, course, doy_posix_week)]

Overlap the two school years:

rt_by_week[school_year == "18/19", doy_posix_week_aligned := as.POSIXct(as.POSIXct(doy_posix_week) + 365*24*60*60, origin = "1970-01-01")]
rt_by_week[school_year == "19/20", doy_posix_week_aligned := as.POSIXct(doy_posix_week)]

Add question type labels:

rt_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
rt_by_week[, school_year := factor(school_year, levels = c("18/19", "19/20"))]

Plot response time by week (mean +/- 1 SE).

p_rt <- ggplot(rt_by_week[(course == "English" & mcq == TRUE) | course == "French",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(. ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1.7, 3.7)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt
Warning: Removed 24 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type.pdf", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type.png", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).

By level and year

Add book info:

rt[book_info[book_type == "Hoofdboek",], on  = "book_info_id", c("method_group", "book_title") := .(i.method_group, i.book_title)]

Simplify level names:

# Keep all distinctions
rt[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
rt[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
rt[, level := dplyr::case_when(
  grepl( "hv", book_title) ~ "General secondary\n(havo)",
  grepl("vmbo", book_title) ~ "Pre-vocational\n(vmbo)",
  grepl("havo", book_title) ~ "General secondary\n(havo)",
  grepl("vwo", book_title) ~ "Pre-university\n(vwo)",
  TRUE ~ "Other")]
rt[, level := factor(level, levels = c("Other", "Pre-vocational\n(vmbo)", "General secondary\n(havo)", "Pre-university\n(vwo)"))]

Simplify year names:

rt[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]
rt_strat_med <- rt[, .(rt_median = median(rt)), by = .(school_year, mcq, user, course, level, year, doy_posix_week)]

rt_strat_by_week <- rt_strat_med[, .(rt = mean(rt_median), rt_se = sd(rt_median)/sqrt(.N)), by = .(school_year, mcq, course, level, year, doy_posix_week)]

Overlap the two school years:

rt_strat_by_week[school_year == "18/19", doy_posix_week_aligned := as.POSIXct(as.POSIXct(doy_posix_week) + 365*24*60*60, origin = "1970-01-01")]
rt_strat_by_week[school_year == "19/20", doy_posix_week_aligned := as.POSIXct(doy_posix_week)]

Add question type labels:

rt_strat_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
rt_strat_by_week[, school_year := factor(school_year, levels = c("18/19", "19/20"))]

Plot response time by week (mean +/- 1 SE).

p_rt_level_year <- ggplot(rt_strat_by_week[course == "French",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1, 4)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt_level_year
Warning: Removed 11 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type_french_level_year.pdf", width = 9, height = 3)
Warning: Removed 11 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type_french_level_year.png", width = 9, height = 3)
Warning: Removed 11 row(s) containing missing values (geom_path).
p_rt_level_year <- ggplot(rt_strat_by_week[course == "English" & question_type == "Multiple\nchoice" & level != "Other",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1, 4)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt_level_year
Warning: Removed 5 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type_english_level_year.pdf", width = 9, height = 3)
Warning: Removed 5 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type_english_level_year.png", width = 9, height = 3)
Warning: Removed 5 row(s) containing missing values (geom_path).

By level

rt_level_med <- rt[, .(rt_median = median(rt)), by = .(school_year, mcq, user, course, level, doy_posix_week)]

rt_level_by_week <- rt_level_med[, .(rt = mean(rt_median), rt_se = sd(rt_median)/sqrt(.N)), by = .(school_year, mcq, course, level, doy_posix_week)]

Overlap the two school years:

rt_level_by_week[school_year == "18/19", doy_posix_week_aligned := as.POSIXct(as.POSIXct(doy_posix_week) + 365*24*60*60, origin = "1970-01-01")]
rt_level_by_week[school_year == "19/20", doy_posix_week_aligned := as.POSIXct(doy_posix_week)]

Add question type labels:

rt_level_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
rt_level_by_week[, school_year := factor(school_year, levels = c("18/19", "19/20"))]

Plot response time by week (mean +/- 1 SE).

p_rt_level <- ggplot(rt_level_by_week[((course == "English" & question_type == "Multiple\nchoice") | course == "French") & level != "Other",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1, 6)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt_level
Warning: Removed 20 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type_level.pdf", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type_level.png", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).

By year

rt_year_med <- rt[, .(rt_median = median(rt)), by = .(school_year, mcq, user, course, year, doy_posix_week)]

rt_year_by_week <- rt_year_med[, .(rt = mean(rt_median), rt_se = sd(rt_median)/sqrt(.N)), by = .(school_year, mcq, course, year, doy_posix_week)]

Overlap the two school years:

rt_year_by_week[school_year == "18/19", doy_posix_week_aligned := as.POSIXct(as.POSIXct(doy_posix_week) + 365*24*60*60, origin = "1970-01-01")]
rt_year_by_week[school_year == "19/20", doy_posix_week_aligned := as.POSIXct(doy_posix_week)]

Add question type labels:

rt_year_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
rt_year_by_week[, school_year := factor(school_year, levels = c("18/19", "19/20"))]

Plot response time by week (mean +/- 1 SE).

p_rt_year <- ggplot(rt_year_by_week[((course == "English" & question_type == "Multiple\nchoice") | course == "French") & year != "Other",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(year ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1, 4)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt_year
Warning: Removed 14 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type_year.pdf", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type_year.png", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).

Regression model

rt_model_dat <- rt[, .(rt_median = median(rt)), by = .(course, school_year, period, doy_posix, mcq, user)]

Fit a generalised linear mixed effects model (assuming a Gamma distribution for RT and an identity link function; Lo & Andrew, 2015) to the daily median RT:

if(!file.exists("../output/m_rt_fit.rds")) {
  m_rt <- glmer(rt_median ~ period*school_year*mcq + (1 | user) + (1 | course),
                 data = rt_model_dat[(course == "English" & mcq == TRUE) | course == "French",],
                 family = Gamma(link = "identity"),
                 nAGQ = 0,
                 control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 1e6)))
  saveRDS(m_rt, "../output/m_rt_fit.rds")
} else {
  m_rt <- readRDS("../output/m_rt_fit.rds")
}

m_rt_summary <- summary(m_rt)
m_rt_summary
Generalized linear mixed model fit by maximum likelihood (Adaptive
  Gauss-Hermite Quadrature, nAGQ = 0) [glmerMod]
 Family: Gamma  ( identity )
Formula: 
rt_median ~ period * school_year * mcq + (1 | user) + (1 | course)
   Data: rt_model_dat[(course == "English" & mcq == TRUE) | course ==  
    "French", ]
Control: 
glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 1e+06))

     AIC      BIC   logLik deviance df.resid 
10332190 10332361 -5166080 10332160   669366 

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
 -2.732  -0.345  -0.070   0.241 220.533 

Random effects:
 Groups   Name        Variance  Std.Dev.
 user     (Intercept) 2.828e+05 531.8041
 course   (Intercept) 2.852e+02  16.8883
 Residual             1.324e-01   0.3639
Number of obs: 669381, groups:  user, 133398; course, 2

Fixed effects:
                                               Estimate Std. Error t value
(Intercept)                                    2133.210     13.019 163.848
periodduring-lockdown                           207.475      6.937  29.909
periodpost-lockdown                             198.282     10.619  18.673
school_year18/19                                 28.172      6.564   4.292
mcqTRUE                                         150.852      5.382  28.028
periodduring-lockdown:school_year18/19         -280.435     11.362 -24.681
periodpost-lockdown:school_year18/19           -294.190     17.057 -17.247
periodduring-lockdown:mcqTRUE                  -210.118      7.464 -28.151
periodpost-lockdown:mcqTRUE                    -199.308     11.721 -17.005
school_year18/19:mcqTRUE                        -62.059      6.894  -9.002
periodduring-lockdown:school_year18/19:mcqTRUE  278.331     12.257  22.708
periodpost-lockdown:school_year18/19:mcqTRUE    315.189     18.735  16.824
                                               Pr(>|z|)    
(Intercept)                                     < 2e-16 ***
periodduring-lockdown                           < 2e-16 ***
periodpost-lockdown                             < 2e-16 ***
school_year18/19                               1.77e-05 ***
mcqTRUE                                         < 2e-16 ***
periodduring-lockdown:school_year18/19          < 2e-16 ***
periodpost-lockdown:school_year18/19            < 2e-16 ***
periodduring-lockdown:mcqTRUE                   < 2e-16 ***
periodpost-lockdown:mcqTRUE                     < 2e-16 ***
school_year18/19:mcqTRUE                        < 2e-16 ***
periodduring-lockdown:school_year18/19:mcqTRUE  < 2e-16 ***
periodpost-lockdown:school_year18/19:mcqTRUE    < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
                  (Intr) prddr- prdps- sc_18/19 mcTRUE prdd-:_18/19
prddrng-lck       -0.251                                           
prdpst-lckd       -0.161  0.334                                    
schl_y18/19       -0.277  0.483  0.311                             
mcqTRUE           -0.335  0.567  0.368  0.619                      
prdd-:_18/19       0.159 -0.617 -0.207 -0.533   -0.352             
prdp-:_18/19       0.105 -0.214 -0.626 -0.356   -0.234  0.229      
prddr-:TRUE        0.226 -0.861 -0.281 -0.434   -0.651  0.530      
prdps-:TRUE        0.143 -0.275 -0.854 -0.275   -0.413  0.170      
s_18/19:TRU        0.245 -0.440 -0.284 -0.879   -0.703  0.483      
prdd-:_18/19:TRUE -0.141  0.529  0.173  0.474    0.403 -0.882      
prdp-:_18/19:TRUE -0.092  0.176  0.537  0.311    0.263 -0.191      
                  prdp-:_18/19 prdd-:TRUE prdp-:TRUE s_18/19:
prddrng-lck                                                  
prdpst-lckd                                                  
schl_y18/19                                                  
mcqTRUE                                                      
prdd-:_18/19                                                 
prdp-:_18/19                                                 
prddr-:TRUE        0.179                                     
prdps-:TRUE        0.534        0.305                        
s_18/19:TRU        0.322        0.490      0.310             
prdd-:_18/19:TRUE -0.195       -0.613     -0.187     -0.539  
prdp-:_18/19:TRUE -0.872       -0.194     -0.627     -0.354  
                  prdd-:_18/19:TRUE
prddrng-lck                        
prdpst-lckd                        
schl_y18/19                        
mcqTRUE                            
prdd-:_18/19                       
prdp-:_18/19                       
prddr-:TRUE                        
prdps-:TRUE                        
s_18/19:TRU                        
prdd-:_18/19:TRUE                  
prdp-:_18/19:TRUE  0.212           

Save coefficients as a table for in the paper:

m_rt_coef <- as.data.frame(m_rt_summary$coefficients)
setDT(m_rt_coef, keep.rownames = TRUE)
m_rt_coef$rn <- c("Intercept \\small{(Period: pre-lockdown, School year: 19/20, Question type: open answer)}",
                   "Period: lockdown",
                   "Period: post-lockdown",
                   "School year: 18/19",
                   "Question type: multiple choice",
                   "Period: lockdown $\\times$ School year: 18/19",
                   "Period: post-lockdown $\\times$ School year: 18/19",
                   "Period: lockdown $\\times$ Question type: multiple choice",
                   "Period: post-lockdown $\\times$ Question type: multiple choice",
                   "School year: 18/19 $\\times$ Question type: multiple choice",
                   "Period: lockdown $\\times$ School year: 18/19 $\\times$ Question type: multiple choice",
                   "Period: post-lockdown $\\times$ School year: 18/19 $\\times$ Question type: multiple choice")

# Format p-values
m_rt_coef$`Pr(>|z|)` <- format.pval(m_rt_coef$`Pr(>|z|)`, eps = .001, digits = 3, flag = "0")
m_rt_coef$`Pr(>|z|)` <- sub('^(<)?0[.]', '\\1.', m_rt_coef$`Pr(>|z|)`) # Remove leading zero

cat(knitr::kable(m_rt_coef,
                 align = c("l","r", "r", "r", "r"),
                 digits = c(NA, 3, 3, 2, NA),
                 col.names = c("Effect", "$b$", "SE", "$z$", "$p$"),
                 format = "latex",
                 booktabs = TRUE,
                 escape = FALSE),
    file = "../output/m_rt_table.tex")

Visualise the model fit:

rt_fit <- expand.grid(period = c("pre-lockdown", "during-lockdown", "post-lockdown"), school_year = c("18/19", "19/20"), mcq = c(TRUE, FALSE))
rt_fit <- cbind(rt_fit, rt = predict(m_rt, type = "response", re.form = NA, newdata = rt_fit))
rt_fit
ggplot(rt_fit, aes(x = period, y = rt, colour = school_year, lty = mcq, group = interaction(mcq, school_year))) +
  geom_line() +
  geom_point() +
  scale_y_continuous(limits = c(1500, 4000)) +
  theme_paper

Empirical means:

rt_mean <- rt_model_dat[(course == "English" & mcq == TRUE) | course == "French", .(rt = mean(rt_median)), by = .(period, school_year, mcq, user, course)][, .(rt = mean(rt), rt_sd = sd(rt)), by = .(period, school_year, mcq, course)]
rt_mean[, school_year := factor(school_year, levels = c("18/19", "19/20"))]
rt_mean
ggplot(rt_mean, aes(x = period, y = rt, colour = school_year, lty = mcq, group = interaction(mcq, school_year))) +
  facet_grid(~ course) +
  geom_line() +
  geom_point() +
  scale_y_continuous(limits = c(1500, 3000)) +
  theme_paper

Combination plot

p_legend <- get_legend(p_acc)
Warning: Removed 24 row(s) containing missing values (geom_path).
p_acc <- p_acc +
  guides(colour = FALSE, fill = FALSE, lty = FALSE)

p_rt <- p_rt +
  guides(colour = FALSE, fill = FALSE, lty = FALSE)

Combine plots:

plot_grid(
  plot_grid(p_acc, p_rt,
            ncol = 1,
            labels = c("A", "B")),
  p_legend,
  rel_widths = c(1, .2)
)
Warning: Removed 24 row(s) containing missing values (geom_path).

Warning: Removed 24 row(s) containing missing values (geom_path).

ggsave("../output/combi_acc_rt.pdf", width = 9, height = 3.5)
ggsave("../output/combi_acc_rt.png", width = 9, height = 3.5)

Learning progress

Get the unique book chapter IDs on each day:

db <- db_connect()

progress <- dbGetQuery(db,
                       "SELECT DISTINCT r.book_info_id AS 'book_info_id',
                        r.method AS 'method',
                        DATE(r.date + 3600, 'unixepoch') AS 'doy',
                        COUNT(*) AS 'trials'
                        FROM 'responses' r
                        GROUP BY r.method,
                        r.book_info_id,
                        DATE(r.date + 3600, 'unixepoch');"
                       )

db_disconnect(db)

setDT(progress)

Join with the book chapter information:

db <- db_connect()
book_info <- dbGetQuery(db, "SELECT DISTINCT * FROM 'book_info'")
db_disconnect(db)

setDT(book_info)
progress[book_info[book_type == "Hoofdboek",], on  = "book_info_id", c("method_group", "book_title", "chapter") := .(i.method_group, i.book_title, i.chapter)]

Add sensible course names:

progress[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Add a school year column (cutoff date: 1 August):

progress[, doy_posix := as.POSIXct(doy)]
progress[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Consolidate count by day and chapter:

progress_by_day <- progress[, .(trials = sum(trials)), by = .(school_year, doy_posix, course, method_group, book_title, chapter)]

Simplify level names:

# Keep all distinctions
progress_by_day[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
progress_by_day[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
progress_by_day[, level := dplyr::case_when(
  grepl( "hv", book_title) ~ "General secondary (havo)",
  grepl("vmbo", book_title) ~ "Pre-vocational (vmbo)",
  grepl("havo", book_title) ~ "General secondary (havo)",
  grepl("vwo", book_title) ~ "Pre-university (vwo)",
  TRUE ~ "Other")]
progress_by_day[, level := factor(level, levels = c("Other", "Pre-vocational (vmbo)", "General secondary (havo)", "Pre-university (vwo)"))]

Simplify year names:

progress_by_day[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]

Simplify chapter names:

# In most cases, the chapter name starts with a number
progress_by_day[, chapter_simple := factor(as.numeric(stringr::str_extract(chapter, "^\\d{1,2}")))]
# Remaining cases:
unique(progress_by_day[is.na(chapter_simple),]$chapter)
 [1] "BS2 Dienstleistung"               
 [2] "BS5 Reisen"                       
 [3] "BS1 Familie und Beziehungen"      
 [4] "BS3 Dienstleistung"               
 [5] "BS4 Reisen und Verkehr"           
 [6] "Lernliste Brückenschlag"          
 [7] "BS2 Freizeit"                     
 [8] "BS1 Schule und Ausbildung"        
 [9] "Bridging the Gap Year 2"          
[10] "Bridging the Gap Year 1"          
[11] "Bridging the Gap Exam Preparation"
[12] "Exam Preparation"                 
[13] "Bridging the Gap mbo"             
[14] "Bridging the Gap havo"            
BS2 Dienstleistung

BS5 Reisen

BS1 Familie und Beziehungen

BS3 Dienstleistung

BS4 Reisen und Verkehr

Lernliste Brückenschlag

BS2 Freizeit

BS1 Schule und Ausbildung

Bridging the Gap Year 2

Bridging the Gap Year 1

Bridging the Gap Exam Preparation

Exam Preparation

Bridging the Gap mbo

Bridging the Gap havo
# Combine these chapters into an "other" category
progress_by_day[is.na(chapter_simple), chapter_simple := "O"]

Align school years:

progress_by_day[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
progress_by_day[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week and month. Each day is assigned the date of the most recent Monday.

progress_by_day[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
progress_by_day[, doy_posix_aligned_month := cut.POSIXt(doy_posix_aligned, "month")]

Calculate proportions by week and month:

progress_by_week <- progress_by_day[, .(trials = sum(trials)), by = .(school_year, doy_posix_aligned_week, course, level, year, chapter_simple)]
progress_by_week[, prop := trials/sum(trials), by = .(school_year, doy_posix_aligned_week, course, level, year)]
progress_by_month <- progress_by_day[, .(trials = sum(trials)), by = .(school_year, doy_posix_aligned_month, course, level, year, chapter_simple)]
progress_by_month[, prop := trials/sum(trials), by = .(school_year, doy_posix_aligned_month, course, level, year)]
setorder(progress_by_week, chapter_simple)
setorder(progress_by_month, chapter_simple)

French

p_french_y1 <- ggplot(progress_by_week[course == "French" & year == "Year 1"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_french_y2 <- ggplot(progress_by_week[course == "French" & year == "Year 2"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_french_y3 <- ggplot(progress_by_week[course == "French" & year == "Year 3/4"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_progress_french <- plot_grid(p_french_y1, p_french_y2, p_french_y3, 
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c("Year 1", "Year 2", "Year 3/4"),
          hjust = -0.1,
          scale = .95)
Warning: Removed 48 rows containing missing values (position_stack).
Warning: Removed 41 rows containing missing values (geom_col).
Warning: Removed 47 rows containing missing values (position_stack).
Warning: Removed 42 rows containing missing values (geom_col).
Warning: Removed 24 rows containing missing values (position_stack).
Warning: Removed 23 rows containing missing values (geom_col).
p_progress_french

ggsave("../output/progress_french.pdf", width = 9, height = 9)
ggsave("../output/progress_french.png", width = 9, height = 6)

Did the share of trials change between school years? We can simplify the analysis by aggregating over the whole lockdown period.

progress_lockdown <- progress_by_day[between(doy_posix_aligned, date_schools_closed, date_schools_opened), .(trials = sum(trials)), by = .(school_year, course, level, year, chapter_simple)]

# Fill in missing rows (occurs when chapter was only studied in one of the two years)
progress_lockdown <- as.data.table(tidyr::complete(progress_lockdown, tidyr::nesting(course, level, year, chapter_simple), school_year, fill = list(trials = 0))) 
  
progress_lockdown[, prop := trials/sum(trials), by = .(school_year, course, level, year)]
setorder(progress_lockdown, chapter_simple)
ggplot(progress_lockdown[course == "French"], aes(x = school_year, y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(level ~ year) +
  geom_col(colour = NA) +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter",
       title = "French") +
  theme_paper

Perform a chi-square test of homogeneity to determine whether school years are significantly different.

for (y in sort(unique(progress_lockdown$year))) {
  for (l in levels(progress_lockdown$level)) {
    d <- progress_lockdown[course == "French" & year == y & level == l]
    if (nrow(d) > 0) {
      print(paste("French", y, l, collapse= " "))
      print(
        chisq.test(
          dcast(d, school_year ~ chapter_simple, value.var = "trials", fill = 0)[, school_year := NULL]
        )
      )
    }
  } 
}
[1] "French Year 1 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 27254, df = 5, p-value < 2.2e-16

[1] "French Year 1 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 218921, df = 5, p-value < 2.2e-16

[1] "French Year 1 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 38951, df = 5, p-value < 2.2e-16

[1] "French Year 2 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 12940, df = 5, p-value < 2.2e-16

[1] "French Year 2 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 50301, df = 5, p-value < 2.2e-16

[1] "French Year 2 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 44901, df = 5, p-value < 2.2e-16

[1] "French Year 3/4 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 30099, df = 7, p-value < 2.2e-16

[1] "French Year 3/4 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 22949, df = 5, p-value < 2.2e-16

[1] "French Year 3/4 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 21144, df = 5, p-value < 2.2e-16

Conclusion: all tests indicate a difference in proportions between school years (p << 0.001).

Visualise the change between school years:

progress_lockdown[, prop_change := prop[school_year == "19/20"] - prop[school_year == "18/19"], by = .(course, level, year, chapter_simple)]
ggplot(progress_lockdown[school_year == "19/20" & course == "French"], aes(x = chapter_simple, y = (prop_change * 100), colour = chapter_simple, group = school_year)) +
  facet_grid(level ~ year, scales = "free_x") +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(xend = chapter_simple), yend = 0) +
  geom_point() +
  scale_y_continuous(limits = c(-25, 25)) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = "Chapter",
       y = "Change in trial share\n(percentage points)",
       title = "French") +
  theme_paper

Are these changes really important? We may expect a certain amount of fluctuation between any pair of school years. We don’t have data from before the 18/19 school year, but we can look at how the magnitude of changes during the lockdown period compares to changes earlier in the school year.

To keep things as comparable as possible, use a sliding time window with the same size as the lockdown period:

time_window <- as.numeric(round(date_schools_opened - date_schools_closed))
time_window
[1] 78
date_range <- sort(unique(progress_by_day$doy_posix_aligned))
date_range <- date_range[date_range < date_schools_closed]

prop_change_window <- data.table()

for (i in 1:(length(date_range) - as.numeric(time_window))) {
  d <- date_range[i:(i + time_window - 1)]
  progress_window <- progress_by_day[course %in% c("French", "English") & doy_posix_aligned %in% d,
                                     .(trials = sum(trials)),
                                     by = .(school_year, course, level, year, chapter_simple)]
  
  # Fill in missing rows (occurs when chapter was only studied in one of the two years)
  progress_window <- as.data.table(tidyr::complete(progress_window, tidyr::nesting(course, level, year, chapter_simple), school_year, fill = list(trials = 0))) 
  
  progress_window[, prop := trials/sum(trials), by = .(school_year, course, level, year)]

  progress_window[, prop_change := prop[school_year == "19/20"] - prop[school_year == "18/19"], by = .(course, level, year, chapter_simple)]
  
  prop_change_window <- rbind(prop_change_window, progress_window[school_year == "19/20",][,window := i][,date_min := min(d)][,date_max := max(d)])
}

The density of year-to-year changes can be visualised by time window:

ggplot(prop_change_window, aes(x = prop_change * 100, y = window, group = window)) +
  ggridges::geom_density_ridges(alpha = 0.1, scale = 25, fill = NA) +
  labs(x = "Change in trial share\n(percentage points)",
         y = "Time window") +
  theme_paper
Picking joint bandwidth of 0.583

Compare the aggregated density to the changes during the lockdown period:

prop_change_combined <- rbind(prop_change_window[, period := "Pre-lockdown"], progress_lockdown[course %in% c("French", "English") & school_year == "19/20", period := "Lockdown"], fill = TRUE)
prop_change_combined[, period := factor(period, levels = c("Pre-lockdown", "Lockdown"))]
ggplot(prop_change_combined, aes(x = prop_change, colour = period)) +
  geom_density() +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = "Change in trial share\n(percentage points)",
       y = "Density",
       colour = NULL) +
  theme_paper

prop_change_sd <- prop_change_window[, .(sd = sd(prop_change) * 100), by = .(course, year, level)]

Add boundaries based on the typical spread to the change plot:

p_change_french <- ggplot(progress_lockdown[school_year == "19/20" & course == "French"], aes(colour = chapter_simple)) +
  facet_grid(year ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "French"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "French"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100))) +
  scale_y_continuous(breaks = c(-20, 0, 20)) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = "Chapter",
       y = "Change in trial share\n(percentage points)") +
  theme_paper +
  theme(panel.grid.major.y = element_blank())

p_change_french

ggsave("../output/progress_change_french.pdf", width = 5, height = 4)
ggsave("../output/progress_change_french.png", width = 9, height = 3)

Make a combination plot for in the paper:

plot_grid(p_french_y1, p_french_y2, p_french_y3, p_change_french,
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c("Year 1", "Year 2", "Year 3/4", "Change"),
          rel_heights = c(1, 1, 1, 1.5),
          hjust = -0.1,
          scale = .95)
Warning: Removed 48 rows containing missing values (position_stack).
Warning: Removed 41 rows containing missing values (geom_col).
Warning: Removed 47 rows containing missing values (position_stack).
Warning: Removed 42 rows containing missing values (geom_col).
Warning: Removed 24 rows containing missing values (position_stack).
Warning: Removed 23 rows containing missing values (geom_col).

ggsave("../output/progress_combi_french.pdf", width = 9, height = 9)
ggsave("../output/progress_combi_french.png", width = 9, height = 9)
p_change_french_y1 <- ggplot(progress_lockdown[school_year == "19/20" & course == "French" & year == "Year 1"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 1"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 1"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-20, 0, 20), limits = c(-30, 30), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_french_y2 <- ggplot(progress_lockdown[school_year == "19/20" & course == "French" & year == "Year 2"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 2"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 2"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-20, 0, 20),  limits = c(-30, 30), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_french_y3 <- ggplot(progress_lockdown[school_year == "19/20" & course == "French" & year == "Year 3/4"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 3/4"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 3/4"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-20, 0, 20),  limits = c(-30, 30), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

filler_plot <- qplot() + 
  theme_nothing() + 
  theme(panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

plot_grid(
          filler_plot,
          p_french_y1, filler_plot, p_change_french_y1, filler_plot,
          p_french_y2, filler_plot, p_change_french_y2, filler_plot, 
          p_french_y3, filler_plot, p_change_french_y3,
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c(NA,
                     "Year 1", NA, NA, NA,
                     "Year 2", NA, NA, NA,
                     "Year 3/4", NA, NA),
          rel_heights = c(.1, 
                          1, -.2, .75, .1,
                          1, -.2, .75, .1,
                          1, -.2, .75),
          hjust = -0.1,
          vjust = -0.1,
          scale = .95)
Warning: Removed 48 rows containing missing values (position_stack).
Warning: Removed 41 rows containing missing values (geom_col).
Warning: Removed 47 rows containing missing values (position_stack).
Warning: Removed 42 rows containing missing values (geom_col).
Warning: Removed 24 rows containing missing values (position_stack).
Warning: Removed 23 rows containing missing values (geom_col).
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

ggsave("../output/progress_combi_alt_french.pdf", width = 9, height = 9)
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).
ggsave("../output/progress_combi_alt_french.png", width = 9, height = 9)
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

English

NOTE: chapters without a number (Bridging the Gap, Exam Preparation) are shown as “O” in the plot. They don’t seem to fit neatly in the chapter sequence, so I’m grouping them together.

p_english_y1 <- ggplot(progress_by_week[level != "Other"][, level := factor(level)][course == "English" & year == "Year 1"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level, drop = FALSE) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_english_y2 <- ggplot(progress_by_week[level != "Other"][, level := factor(level)][course == "English" & year == "Year 2"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level, drop = FALSE) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_english_y3 <- ggplot(progress_by_week[level != "Other"][, level := factor(level)][course == "English" & year == "Year 3"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level, drop = FALSE) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_english_y4 <- ggplot(progress_by_week[level != "Other"][, level := factor(level)][course == "English" & year == "Year 4"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level, drop = FALSE) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_progress_english <- plot_grid(p_english_y1, p_english_y2, p_english_y3, p_english_y4,
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c("Year 1", "Year 2", "Year 3", "Year 4"),
          hjust = -0.1,
          scale = .95)
Warning: Removed 61 rows containing missing values (position_stack).
Warning: Removed 61 rows containing missing values (geom_col).
Warning: Removed 71 rows containing missing values (position_stack).
Warning: Removed 61 rows containing missing values (geom_col).
Warning: Removed 49 rows containing missing values (position_stack).
Warning: Removed 50 rows containing missing values (geom_col).
Warning: Removed 7 rows containing missing values (position_stack).
Warning: Removed 4 rows containing missing values (geom_col).
p_progress_english

ggsave("../output/progress_english.pdf", width = 9, height = 9)
ggsave("../output/progress_english.png", width = 9, height = 9)

Did the share of trials change between school years?

ggplot(progress_lockdown[course == "English" & level != "Other"], aes(x = school_year, y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(level ~ year) +
  geom_col(colour = NA) +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter",
       title = "English") +
  theme_paper

Change between school years:

p_change_english <- ggplot(progress_lockdown[school_year == "19/20" & course == "English" & level != "Other"], aes(colour = chapter_simple)) +
  facet_grid(year ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100))) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = "Chapter",
       y = "Change in trial share\n(percentage points)") +
  theme_paper +
  theme(panel.grid.major.y = element_blank())

p_change_english 

ggsave("../output/progress_change_english.pdf", width = 9, height = 6)
ggsave("../output/progress_change_english.png", width = 9, height = 6)

Perform a chi-square test of homogeneity to determine whether school years are significantly different.

for (y in sort(unique(progress_lockdown$year))) {
  for (l in levels(progress_lockdown$level)) {
    d <- progress_lockdown[course == "English" & year == y & level == l]
    if (nrow(d) > 0) {
      print(paste("English", y, l, collapse= " "))
      print(
        chisq.test(
          dcast(d, school_year ~ chapter_simple, value.var = "trials", fill = 0)[, school_year := NULL]
        )
      )
    }
  } 
}
[1] "English Year 1 Other"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 1907.7, df = 7, p-value < 2.2e-16

[1] "English Year 1 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 57847, df = 7, p-value < 2.2e-16

[1] "English Year 1 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 237876, df = 7, p-value < 2.2e-16

[1] "English Year 1 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 47518, df = 7, p-value < 2.2e-16

[1] "English Year 2 Other"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 4115.7, df = 7, p-value < 2.2e-16

[1] "English Year 2 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 25974, df = 8, p-value < 2.2e-16

[1] "English Year 2 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 15326, df = 8, p-value < 2.2e-16

[1] "English Year 2 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 81417, df = 7, p-value < 2.2e-16

[1] "English Year 3 Other"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 16118, df = 6, p-value < 2.2e-16

[1] "English Year 3 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 19636, df = 6, p-value < 2.2e-16

[1] "English Year 3 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 59552, df = 8, p-value < 2.2e-16

[1] "English Year 3 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 41150, df = 7, p-value < 2.2e-16

[1] "English Year 4 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 20200, df = 5, p-value < 2.2e-16

Conclusion: all tests indicate a difference in proportions between school years (p << 0.001).

Make a combination plot for in the paper:

progress_lockdown_english <- progress_lockdown[level != "Other" & school_year == "19/20" & course == "English"]
progress_lockdown_english[, level := factor(level)]
p_change_english_y1 <- ggplot(progress_lockdown_english[year == "Year 1"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x", drop = FALSE) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 1"][,level := factor(level)], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 1"][,level := factor(level)], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-10, 0, 10), limits = c(-20, 20), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_english_y2 <- ggplot(progress_lockdown_english[year == "Year 2"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x", drop = FALSE) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 2"][,level := factor(level)], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 2"][,level := factor(level)], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-10, 0, 10), limits = c(-20, 20), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_english_y3 <- ggplot(progress_lockdown_english[year == "Year 3"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x", drop = FALSE) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 3"][,level := factor(level)], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 3"][,level := factor(level)], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-10, 0, 10), limits = c(-20, 20), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_english_y4 <- ggplot(progress_lockdown_english[year == "Year 4"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x", drop = FALSE) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 4"][,level := factor(level)], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 4"][,level := factor(level)], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-10, 0, 10), limits = c(-20, 20), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

filler_plot <- qplot() + 
  theme_nothing() + 
  theme(panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

plot_grid(
          filler_plot,
          p_english_y1, filler_plot, p_change_english_y1, filler_plot,
          p_english_y2, filler_plot, p_change_english_y2, filler_plot, 
          p_english_y3, filler_plot, p_change_english_y3, filler_plot,
          p_english_y4, filler_plot, p_change_english_y4,
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c(NA,
                     "Year 1", NA, NA, NA,
                     "Year 2", NA, NA, NA,
                     "Year 3", NA, NA, NA,
                     "Year 4", NA, NA),
          rel_heights = c(.1, 
                          1, -.2, .75, .1,
                          1, -.2, .75, .1,
                          1, -.2, .75, .1,
                          1, -.2, .75),
          hjust = -0.1,
          vjust = -0.1,
          scale = .95)
Warning: Removed 61 rows containing missing values (position_stack).
Warning: Removed 61 rows containing missing values (geom_col).
Warning: Removed 71 rows containing missing values (position_stack).
Warning: Removed 61 rows containing missing values (geom_col).
Warning: Removed 49 rows containing missing values (position_stack).
Warning: Removed 50 rows containing missing values (geom_col).
Warning: Removed 7 rows containing missing values (position_stack).
Warning: Removed 4 rows containing missing values (geom_col).
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

ggsave("../output/progress_combi_alt_english.pdf", width = 9, height = 11)
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).
ggsave("../output/progress_combi_alt_english.png", width = 9, height = 11)
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Session info

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=nl_NL.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=nl_NL.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=nl_NL.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] lmerTest_3.1-0    lme4_1.1-21       Matrix_1.2-18     cowplot_0.9.4    
[5] ggplot2_3.3.2     DBI_1.1.0         data.table_1.13.6

loaded via a namespace (and not attached):
 [1] tidyselect_0.2.5    xfun_0.21           purrr_0.3.2        
 [4] splines_3.6.3       lattice_0.20-41     colorspace_1.4-1   
 [7] vctrs_0.2.2         htmltools_0.3.6     viridisLite_0.3.0  
[10] yaml_2.2.0          blob_1.2.1          rlang_0.4.4        
[13] pillar_1.4.2        nloptr_1.2.1        glue_1.3.1         
[16] withr_2.3.0         bit64_0.9-7         plyr_1.8.4         
[19] lifecycle_0.1.0     stringr_1.4.0       munsell_0.5.0      
[22] gtable_0.3.0        evaluate_0.14       memoise_1.1.0      
[25] labeling_0.3        knitr_1.23          Rcpp_1.0.2         
[28] scales_1.0.0        jsonlite_1.6        bit_1.1-14         
[31] digest_0.6.19       stringi_1.4.3       dplyr_0.8.3        
[34] numDeriv_2016.8-1.1 tools_3.6.3         magrittr_1.5       
[37] tibble_2.1.3        RSQLite_2.2.0       crayon_1.3.4       
[40] tidyr_1.0.0         pkgconfig_2.0.2     MASS_7.3-51.4      
[43] ellipsis_0.3.0      ggridges_0.5.1      assertthat_0.2.1   
[46] minqa_1.2.4         rmarkdown_2.6       R6_2.4.0           
[49] boot_1.3-25         nlme_3.1-149        compiler_3.6.3     
LS0tCnRpdGxlOiAnU2xpbVN0YW1wZW4gUGVyZm9ybWFuY2UgRHVyaW5nIExvY2tkb3duJwphdXRob3I6ICJNYWFydGVuIHZhbiBkZXIgVmVsZGUiCmRhdGU6ICJMYXN0IHVwZGF0ZWQ6IGByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgZ2l0aHViX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICBodG1sX25vdGVib29rOgogICAgc21hcnQ6IG5vCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgojIFNldHVwCgpgYGB7cn0KbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KERCSSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShsbWU0KQpsaWJyYXJ5KGxtZXJUZXN0KQoKU3lzLnNldGxvY2FsZSgiTENfVElNRSIsICJlbl9VUy5VVEYtOCIpICMgUHJpbnQgRW5nbGlzaCBkYXRlIGZvcm1hdAojIFN5cy5zZXRsb2NhbGUoIkxDX1RJTUUiLCAibmxfTkwuVVRGLTgiKSAjIFByaW50IER1dGNoIGRhdGUgZm9ybWF0CgpudW1iZXJfZm9ybWF0IDwtIHNjYWxlczo6bnVtYmVyX2Zvcm1hdChiaWcubWFyayA9ICIsIiwgZGVjaW1hbC5tYXJrID0gIi4iKSAjIFByaW50IEVuZ2xpc2ggbnVtYmVyIGZvcm1hdAojIG51bWJlcl9mb3JtYXQgPC0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KGJpZy5tYXJrID0gIi4iLCBkZWNpbWFsLm1hcmsgPSAiLCIpICMgUHJpbnQgRHV0Y2ggbnVtYmVyIGZvcm1hdAoKdGhlbWVfcGFwZXIgPC0gdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxMikgKyAKICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3VyID0gImJsYWNrIiksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJncmV5OTIiKSkKYGBgCgpTY2hvb2wgY2xvc3VyZSBhbmQgb3BlbmluZyBkYXRlcwoKU291cmNlczoKLSBodHRwczovL3d3dy5yaWprc292ZXJoZWlkLm5sL2FjdHVlZWwvbmlldXdzLzIwMjAvMDMvMTUvYWFudnVsbGVuZGUtbWFhdHJlZ2VsZW4tb25kZXJ3aWpzLWhvcmVjYS1zcG9ydAotIGh0dHBzOi8vd3d3LnJpamtzb3ZlcmhlaWQubmwvYWN0dWVlbC9uaWV1d3MvMjAyMC8wNS8xOS9vbmRlcndpanMtZ2FhdC1zdGFwLXZvb3Itc3RhcC1vcGVuCmBgYHtyfQpkYXRlX3NjaG9vbHNfY2xvc2VkIDwtIGFzLlBPU0lYY3QoIjIwMjAtMDMtMTYiKQpkYXRlX3NjaG9vbHNfb3BlbmVkIDwtIGFzLlBPU0lYY3QoIjIwMjAtMDYtMDIiKQpgYGAKCgpIYW5kbGUgZGF0YWJhc2UgY29ubmVjdGlvbnMKYGBge3J9CmRiX2Nvbm5lY3QgPC0gZnVuY3Rpb24oKSB7CiAgZGIgPC0gZGJDb25uZWN0KFJTUUxpdGU6OlNRTGl0ZSgpLCBmaWxlLnBhdGgoIi4uIiwgImRhdGEiLCAibm9vcmRob2ZmLnNxbGl0ZSIpKQogIHJldHVybihkYikKfQoKZGJfZGlzY29ubmVjdCA8LSBmdW5jdGlvbihkYikgewogIGRiRGlzY29ubmVjdChkYikKfQpgYGAKCgojIERhdGEKClRoZSBkYXRhYmFzZSBjb250YWlucyBhbGwgU2xpbVN0YW1wZW4gZGF0YSBjb2xsZWN0ZWQgdmlhIE5vb3JkaG9mZidzIHBsYXRmb3JtIGluIHRocmVlIGNvdXJzZXM6ICpTdGVwcGluZyBTdG9uZXMqIChFbmdsaXNoKSwgKkdyYW5kZXMgTGlnbmVzKiAoRnJlbmNoKSwgYW5kICpOZXVlIEtvbnRha3RlKiAoR2VybWFuKS4KClRyaWFsLWxldmVsIHJlc3BvbnNlIGRhdGEgYXJlIHN0b3JlZCBpbiB0aGUgYHJlc3BvbnNlc2AgdGFibGUuCkJvb2sgaW5mb3JtYXRpb24sIHN1Y2ggYXMgdGhlIGNvdXJzZSB5ZWFyLCBib29rIHRpdGxlLCBhbmQgY2hhcHRlciwgYXJlIHN0b3JlZCBpbiB0aGUgYGJvb2tfaW5mb2AgdGFibGUuCgojIyBgcmVzcG9uc2VzYAoKfCBDb2x1bW4gICAgICAgICAgICAgICB8IFR5cGUgICAgICB8IEV4cGxhbmF0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8IGRhdGUgICAgICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgVU5JWCB0aW1lIHN0YW1wIFtzXSAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCB1c2VyX2lkICAgICAgICAgICAgICB8IGNociAgICAgICB8IHVuaXF1ZSB1c2VyIGlkZW50aWZpZXIgICAgICAgICAgICAgICAgICAgICAgICB8CnwgbWV0aG9kICAgICAgICAgICAgICAgfCBjaHIgICAgICAgfCBjb3Vyc2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IHN0YXJ0X3RpbWUgICAgICAgICAgIHwgaW50ICAgICAgIHwgZWxhcHNlZCB0aW1lIHNpbmNlIHNlc3Npb24gc3RhcnQgW21zXSAgICAgICAgIHwKfCBydCAgICAgICAgICAgICAgICAgICB8IGludCAgICAgICB8IHJlc3BvbnNlIHRpbWUgW21zXSAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgZHVyYXRpb24gICAgICAgICAgICAgfCBpbnQgICAgICAgfCB0cmlhbCBkdXJhdGlvbiBbbXNdICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGZhY3RfaWQgICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgdW5pcXVlIGZhY3QgaWRlbnRpZmllciAod2l0aGluIGNoYXB0ZXIpICAgICAgIHwKfCBjb3JyZWN0ICAgICAgICAgICAgICB8IGludCAgICAgICB8IHJlc3BvbnNlIGFjY3VyYWN5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgYW5zd2VyICAgICAgICAgICAgICAgfCBjaHIgICAgICAgfCB1c2VyJ3MgcmVzcG9uc2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGNob2ljZXMgICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgbnVtYmVyIG9mIGFuc3dlciBjaG9pY2VzICgxID09IG9wZW4gcmVzcG9uc2UpIHwKfCBiYWNrc3BhY2VfdXNlZCAgICAgICB8IGRibCAgICAgICB8IHVzZXIgcHJlc3NlZCBiYWNrc3BhY2UgZHVyaW5nIHRyaWFsICAgICAgICAgICB8CnwgYmFja3NwYWNlX3VzZWRfZmlyc3QgfCBkYmwgICAgICAgfCB1c2VyIGVyYXNlZCBmaXJzdCBjaGFyYWN0ZXIgb2YgcmVzcG9uc2UgICAgICAgfAp8IHN0dWR5ICAgICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgdHJpYWwgd2FzIGEgc3R1ZHkgdHJpYWwgICAgICAgICAgICAgICAgICAgICAgIHwKfCBhbnN3ZXJfbGFuZ3VhZ2UgICAgICB8IGNociAgICAgICB8IGxhbmd1YWdlIG9mIHRoZSBhbnN3ZXIgICAgICAgICAgICAgICAgICAgICAgICB8Cnwgc3Vic2Vzc2lvbiAgICAgICAgICAgfCBpbnQgICAgICAgfCBpZGVudGlmaWVzIHBhcnQgd2l0aGluIGxlYXJuaW5nIHNlc3Npb24gICAgICAgfAp8IGJvb2tfaW5mb19pZCAgICAgICAgIHwgY2hyICAgICAgIHwgdW5pcXVlIGlkZW50aWZpZXIgb2YgYm9vayBpbmZvcm1hdGlvbiAgICAgICAgIHwKCgojIyBgYm9va19pbmZvYAoKfCBDb2x1bW4gICAgICAgICAgICAgICB8IFR5cGUgICAgICB8IEV4cGxhbmF0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8IGJvb2tfaW5mb19pZCAgICAgICAgIHwgY2hyICAgICAgIHwgdW5pcXVlIGlkZW50aWZpZXIgb2YgYm9vayBpbmZvcm1hdGlvbiAgICAgICAgIHwKfCBtZXRob2RfZ3JvdXAgICAgICAgICB8IGNociAgICAgICB8IHllYXIgYW5kIGVkaXRpb24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgYm9va190aXRsZSAgICAgICAgICAgfCBjaHIgICAgICAgfCBib29rIHRpdGxlIChpbmNsLiB5ZWFyLCBsZXZlbCwgZWRpdGlvbikgICAgICAgfAp8IGJvb2tfdHlwZSAgICAgICAgICAgIHwgY2hyICAgICAgIHwgdHlwZSBvZiBib29rICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBjaGFwdGVyICAgICAgICAgICAgICB8IGNociAgICAgICB8IGNoYXB0ZXIgbnVtYmVyIGFuZCB0aXRsZSAgICAgICAgICAgICAgICAgICAgICB8CgoKUHJldmlldyBmaXJzdCAxMCByb3dzCmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKcmVzcG9uc2VzX3RvcCA8LSBkYkdldFF1ZXJ5KGRiLCAiU0VMRUNUICogRlJPTSByZXNwb25zZXMgTElNSVQgMTAiKQpyZXNwb25zZXNfdG9wCgpib29rX2luZm9fdG9wIDwtIGRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgKiBGUk9NIGJvb2tfaW5mbyBMSU1JVCAxMCIpCmJvb2tfaW5mb190b3AKZGJfZGlzY29ubmVjdChkYikKYGBgCgoKIyBQZXJmb3JtYW5jZQoKVGhlcmUgYXJlIHNldmVyYWwgbWVhc3VyZXMgb2YgbGVhcm5pbmcgcGVyZm9ybWFuY2Ugd2UgY2FuIGxvb2sgYXQuClRoZSBtb3N0IHN0cmFpZ2h0LWZvcndhcmQgb2YgdGhlc2UgYXJlIHJlc3BvbnNlIGFjY3VyYWN5IGFuZCByZXNwb25zZSB0aW1lLgoKSW1wb3J0YW50IGZhY3RvcnMgdG8ga2VlcCBpbiBtaW5kOiBxdWVzdGlvbiB0eXBlIChtdWx0aXBsZSBjaG9pY2Ugb3Igb3BlbiBhbnN3ZXIpIGFuZCBsYW5ndWFnZS4KTm90ZSB0aGF0IHdlIGNhbm5vdCBkaXN0aW5ndWlzaCBiZXR3ZWVuIE5MLVggYW5kIFgtWCwgc2luY2Ugd2Ugb25seSBrbm93IHRoZSBsYW5ndWFnZSBvZiB0aGUgYW5zd2VyLgoKIyMgUmVzcG9uc2UgYWNjdXJhY3kKCiMjIyBXaG9sZSBwb3B1bGF0aW9uCgpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCmNvcnJlY3QgPC0gZGJHZXRRdWVyeShkYiwgCiAgICAgICAgICAgICAgICAgICAgICAiU0VMRUNUIHIubWV0aG9kIEFTICdtZXRob2QnLAogICAgICAgICAgICAgICAgICAgICAgREFURShyLmRhdGUgKyAzNjAwLCAndW5peGVwb2NoJykgQVMgJ2RveScsCiAgICAgICAgICAgICAgICAgICAgICByLnVzZXJfaWQgQVMgJ3VzZXInLAogICAgICAgICAgICAgICAgICAgICAgci5jaG9pY2VzID4gMSBBUyAnbWNxJywKICAgICAgICAgICAgICAgICAgICAgIHIuY29ycmVjdCBBUyAnY29ycmVjdCcsCiAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAnbicKICAgICAgICAgICAgICAgICAgICAgIEZST00gJ3Jlc3BvbnNlcycgcgogICAgICAgICAgICAgICAgICAgICAgV0hFUkUgci5zdHVkeSA9PSAwCiAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSByLm1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgIERBVEUoci5kYXRlICsgMzYwMCwgJ3VuaXhlcG9jaCcpLAogICAgICAgICAgICAgICAgICAgICAgci51c2VyX2lkLAogICAgICAgICAgICAgICAgICAgICAgci5jaG9pY2VzID4gMSwKICAgICAgICAgICAgICAgICAgICAgIHIuY29ycmVjdCIKKQpzZXREVChjb3JyZWN0KQpkYl9kaXNjb25uZWN0KGRiKQpgYGAKCkZpbGwgaW4gbWlzc2luZyByb3dzICh3aGVyZSBhbGwgdHJpYWxzIG9uIGEgZGF5IHdlcmUgY29ycmVjdC9pbmNvcnJlY3QpOgpgYGB7cn0KY29ycmVjdCA8LSB0aWR5cjo6Y29tcGxldGUoY29ycmVjdCwgdGlkeXI6Om5lc3RpbmcobWV0aG9kLCBkb3ksIHVzZXIsIG1jcSksIGNvcnJlY3QsIGZpbGwgPSBsaXN0KG4gPSAwKSkKc2V0RFQoY29ycmVjdCkKYGBgCgpgYGB7cn0KY29ycmVjdFssIG1jcSA6PSBhcy5sb2dpY2FsKG1jcSldCmBgYAoKYGBge3J9CmFjY3VyYWN5IDwtIGNvcnJlY3RbLCAuKGFjY3VyYWN5ID0gbltjb3JyZWN0ID09IDFdL3N1bShuKSwgbiA9IHN1bShuKSksIGJ5ID0gLihtZXRob2QsIGRveSwgdXNlciwgbWNxKV0KYGBgCgpBZGQgYSBzY2hvb2wgeWVhciBjb2x1bW4gKGN1dG9mZiBkYXRlOiAxIEF1Z3VzdCk6CmBgYHtyfQphY2N1cmFjeVssIGRveV9wb3NpeCA6PSBhcy5QT1NJWGN0KGRveSldCmFjY3VyYWN5Wywgc2Nob29sX3llYXIgOj0gaWZlbHNlKGRveV9wb3NpeCA8ICIyMDE5LTA4LTAxIiwgIjE4LzE5IiwgIjE5LzIwIildCmBgYAoKQWRkIHNlbnNpYmxlIGNvdXJzZSBuYW1lczoKYGBge3J9CmFjY3VyYWN5WywgY291cnNlIDo9IGlmZWxzZShtZXRob2QgPT0gIkdyYW5kZXMgTGlnbmVzIiwgIkZyZW5jaCIsIGlmZWxzZShtZXRob2QgPT0gIlN0ZXBwaW5nIFN0b25lcyIsICJFbmdsaXNoIiwgIkdlcm1hbiIpKV0KYGBgCgpBbGlnbiBzY2hvb2wgeWVhcnM6CmBgYHtyfQphY2N1cmFjeVtzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KYWNjdXJhY3lbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKClVzZSBjdXQuRGF0ZSgpIHRvIGJpbiBkYXRlcyBieSB3ZWVrLiBFYWNoIGRheSBpcyBhc3NpZ25lZCB0aGUgZGF0ZSBvZiB0aGUgbW9zdCByZWNlbnQgTW9uZGF5LgpgYGB7cn0KYWNjdXJhY3lbLCBkb3lfcG9zaXhfd2VlayA6PSBjdXQuUE9TSVh0KGRveV9wb3NpeCwgIndlZWsiKV0KYWNjdXJhY3lbLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJ3ZWVrIildCmBgYAoKYGBge3J9CmFjY3VyYWN5X2J5X3dlZWtfYW5kX3VzZXIgPC0gYWNjdXJhY3lbLCAuKGFjY3VyYWN5ID0gc3VtKGFjY3VyYWN5Km4pL3N1bShuKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCB1c2VyLCBtY3EpXQphY2N1cmFjeV9ieV93ZWVrIDwtIGFjY3VyYWN5X2J5X3dlZWtfYW5kX3VzZXJbLCAuKGFjY3VyYWN5X21lYW4gPSBtZWFuKGFjY3VyYWN5LCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY2N1cmFjeV9zZSA9IHNkKGFjY3VyYWN5LCBuYS5ybSA9IFRSVUUpL3NxcnQoLk4pLCBuID0gLk4pLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfd2VlaywgbWNxKV0KYGBgCgoKYGBge3J9CiMgYWNjdXJhY3lfYnlfd2Vla19hbmRfdXNlciA8LSBhY2N1cmFjeVssIC4oYWNjdXJhY3kgPSBtZWFuKGFjY3VyYWN5LCBuYS5ybSA9IFRSVUUpKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIsIGRveV9wb3NpeF9hbGlnbmVkX3dlZWssIHVzZXIsIG1jcSldCiMgYWNjdXJhY3lfYnlfd2VlayA8LSBhY2N1cmFjeV9ieV93ZWVrX2FuZF91c2VyWywgLihhY2N1cmFjeV9tZWFuID0gbWVhbihhY2N1cmFjeSwgbmEucm0gPSBUUlVFKSwKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY2N1cmFjeV9zZSA9IHNkKGFjY3VyYWN5LCBuYS5ybSA9IFRSVUUpL3NxcnQoLk4pLCBuID0gLk4pLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfd2VlaywgbWNxKV0KYGBgCgpBZGQgcXVlc3Rpb24gdHlwZSBsYWJlbHM6CmBgYHtyfQphY2N1cmFjeV9ieV93ZWVrWywgcXVlc3Rpb25fdHlwZSA6PSBpZmVsc2UobWNxID09IFRSVUUsICJNdWx0aXBsZVxuY2hvaWNlIiwgIk9wZW5cbmFuc3dlciIpXQpgYGAKCgpQbG90IHJlc3BvbnNlIGFjY3VyYWN5IGJ5IHdlZWsgKG1lYW4gKy8tIDEgU0UpLgpgYGB7cn0KcF9hY2MgPC0gZ2dwbG90KGFjY3VyYWN5X2J5X3dlZWtbKGNvdXJzZSA9PSAiRW5nbGlzaCIgJiBtY3EgPT0gVFJVRSkgfCBjb3Vyc2UgPT0gIkZyZW5jaCIsXSwKICAgICAgICAgICAgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gYWNjdXJhY3lfbWVhbiwgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZCguIH4gY291cnNlKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IDAsIHltYXggPSAxLjA1LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyLCBhbHBoYSA9IC45KSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBhY2N1cmFjeV9tZWFuIC0gYWNjdXJhY3lfc2UsIHltYXggPSBhY2N1cmFjeV9tZWFuICsgYWNjdXJhY3lfc2UsIGNvbG91ciA9IE5VTEwpLCBhbHBoYSA9IDAuMikgKwogIGdlb21fbGluZShhZXMobHR5ID0gcXVlc3Rpb25fdHlwZSkpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYyguNywgMSksIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJBY2N1cmFjeSIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBsdHkgPSAiUXVlc3Rpb24gdHlwZSIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgbHR5ID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMikpICsKICB0aGVtZV9wYXBlcgpwX2FjYwpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZS5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L2FjY19ieV9xdWVzdGlvbl90eXBlLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKYGBgCgoKIyMjIEJ5IGxldmVsIGFuZCB5ZWFyCgpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCmNvcnJlY3Rfc3RyYXQgPC0gZGJHZXRRdWVyeShkYiwgCiAgICAgICAgICAgICAgICAgICAgICAiU0VMRUNUIHIubWV0aG9kIEFTICdtZXRob2QnLAogICAgICAgICAgICAgICAgICAgICAgci5ib29rX2luZm9faWQgQVMgJ2Jvb2tfaW5mb19pZCcsCiAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSBBUyAnZG95JywKICAgICAgICAgICAgICAgICAgICAgIHIudXNlcl9pZCBBUyAndXNlcicsCiAgICAgICAgICAgICAgICAgICAgICByLmNob2ljZXMgPiAxIEFTICdtY3EnLAogICAgICAgICAgICAgICAgICAgICAgci5jb3JyZWN0IEFTICdjb3JyZWN0JywKICAgICAgICAgICAgICAgICAgICAgIENPVU5UKCopIEFTICduJwogICAgICAgICAgICAgICAgICAgICAgRlJPTSAncmVzcG9uc2VzJyByCiAgICAgICAgICAgICAgICAgICAgICBXSEVSRSByLnN0dWR5ID09IDAKICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHIubWV0aG9kLAogICAgICAgICAgICAgICAgICAgICAgci5ib29rX2luZm9faWQsCiAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSwKICAgICAgICAgICAgICAgICAgICAgIHIudXNlcl9pZCwKICAgICAgICAgICAgICAgICAgICAgIHIuY2hvaWNlcyA+IDEsCiAgICAgICAgICAgICAgICAgICAgICByLmNvcnJlY3QiCikKc2V0RFQoY29ycmVjdF9zdHJhdCkKZGJfZGlzY29ubmVjdChkYikKYGBgCgpGaWxsIGluIG1pc3Npbmcgcm93cyAod2hlcmUgYWxsIHRyaWFscyBvbiBhIGRheSB3ZXJlIGNvcnJlY3QvaW5jb3JyZWN0KToKYGBge3J9CmNvcnJlY3Rfc3RyYXQgPC0gdGlkeXI6OmNvbXBsZXRlKGNvcnJlY3Rfc3RyYXQsIHRpZHlyOjpuZXN0aW5nKG1ldGhvZCwgYm9va19pbmZvX2lkLCBkb3ksIHVzZXIsIG1jcSksIGNvcnJlY3QsIGZpbGwgPSBsaXN0KG4gPSAwKSkKc2V0RFQoY29ycmVjdF9zdHJhdCkKYGBgCgpgYGB7cn0KY29ycmVjdF9zdHJhdFssIG1jcSA6PSBhcy5sb2dpY2FsKG1jcSldCmBgYAoKQWRkIGJvb2sgaW5mb3JtYXRpb246CmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKYm9va19pbmZvIDwtIGRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgRElTVElOQ1QgKiBGUk9NICdib29rX2luZm8nIikKZGJfZGlzY29ubmVjdChkYikKCnNldERUKGJvb2tfaW5mbykKYGBgCgpgYGB7cn0KY29ycmVjdF9zdHJhdFtib29rX2luZm9bYm9va190eXBlID09ICJIb29mZGJvZWsiLF0sIG9uICA9ICJib29rX2luZm9faWQiLCBjKCJtZXRob2RfZ3JvdXAiLCAiYm9va190aXRsZSIpIDo9IC4oaS5tZXRob2RfZ3JvdXAsIGkuYm9va190aXRsZSldCmBgYAoKQWRkIHNlbnNpYmxlIGNvdXJzZSBuYW1lczoKYGBge3J9CmNvcnJlY3Rfc3RyYXRbLCBjb3Vyc2UgOj0gaWZlbHNlKG1ldGhvZCA9PSAiR3JhbmRlcyBMaWduZXMiLCAiRnJlbmNoIiwgaWZlbHNlKG1ldGhvZCA9PSAiU3RlcHBpbmcgU3RvbmVzIiwgIkVuZ2xpc2giLCAiR2VybWFuIikpXQpgYGAKCkFkZCBhIHNjaG9vbCB5ZWFyIGNvbHVtbiAoY3V0b2ZmIGRhdGU6IDEgQXVndXN0KToKYGBge3J9CmNvcnJlY3Rfc3RyYXRbLCBkb3lfcG9zaXggOj0gYXMuUE9TSVhjdChkb3kpXQpjb3JyZWN0X3N0cmF0Wywgc2Nob29sX3llYXIgOj0gaWZlbHNlKGRveV9wb3NpeCA8ICIyMDE5LTA4LTAxIiwgIjE4LzE5IiwgIjE5LzIwIildCmBgYAoKU2ltcGxpZnkgbGV2ZWwgbmFtZXM6CmBgYHtyfQojIEtlZXAgYWxsIGRpc3RpbmN0aW9ucwpjb3JyZWN0X3N0cmF0WywgYm9va190aXRsZV9zaW1wbGUgOj0gc3RyaW5ncjo6c3RyX3N1Yihib29rX3RpdGxlLCAzLCAtMTApXQpjb3JyZWN0X3N0cmF0WywgYm9va190aXRsZV9zaW1wbGUgOj0gZmFjdG9yKGJvb2tfdGl0bGVfc2ltcGxlLCBsZXZlbHMgPSBjKCJ2bWJvIGIvbHdvbyIsICJ2bWJvIGIiLCAidm1ibyBiayIsICJ2bWJvIGsiLCAidm1ibyBrZ3QiLCAidm1iby1ndCIsICJ2bWJvIGd0IiwgInZtYm8tZ3QvaGF2byIsICJ2bWJvICh0KWh2IiwgImhhdm8iLCAiaGF2byB2d28iLCAidndvIikpXQoKIyBTaW1wbGlmeSB0byB0aHJlZSBsZXZlbHMKY29ycmVjdF9zdHJhdFssIGxldmVsIDo9IGRwbHlyOjpjYXNlX3doZW4oCiAgZ3JlcGwoICJodiIsIGJvb2tfdGl0bGUpIH4gIkdlbmVyYWwgc2Vjb25kYXJ5XG4oaGF2bykiLAogIGdyZXBsKCJ2bWJvIiwgYm9va190aXRsZSkgfiAiUHJlLXZvY2F0aW9uYWxcbih2bWJvKSIsCiAgZ3JlcGwoImhhdm8iLCBib29rX3RpdGxlKSB+ICJHZW5lcmFsIHNlY29uZGFyeVxuKGhhdm8pIiwKICBncmVwbCgidndvIiwgYm9va190aXRsZSkgfiAiUHJlLXVuaXZlcnNpdHlcbih2d28pIiwKICBUUlVFIH4gIk90aGVyIildCmNvcnJlY3Rfc3RyYXRbLCBsZXZlbCA6PSBmYWN0b3IobGV2ZWwsIGxldmVscyA9IGMoIk90aGVyIiwgIlByZS12b2NhdGlvbmFsXG4odm1ibykiLCAiR2VuZXJhbCBzZWNvbmRhcnlcbihoYXZvKSIsICJQcmUtdW5pdmVyc2l0eVxuKHZ3bykiKSldCmBgYAoKU2ltcGxpZnkgeWVhciBuYW1lczoKYGBge3J9CmNvcnJlY3Rfc3RyYXRbLCB5ZWFyIDo9IGRwbHlyOjpjYXNlX3doZW4oCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAxICg1ZSBFZC4pIiB+ICJZZWFyIDEiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMiAoNWUgRWQuKSIgfiAiWWVhciAyIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDMgKDVlIEVkLikiIH4gIlllYXIgMyIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAzLzQgKDVlIEVkLikiIH4gIlllYXIgMy80IiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDQgKDVlIEVkLikiIH4gIlllYXIgNCIsCiAgbWV0aG9kX2dyb3VwID09ICJUd2VlZGUgRmFzZSAoNmUgRWQuKSIgfiAiVHdlZWRlIEZhc2UiLAogIFRSVUUgfiAiT3RoZXIiKV0KYGBgCgpDb25zb2xpZGF0ZSBieSBkYXk6CmBgYHtyfQphY2N1cmFjeV9zdHJhdCA8LSBjb3JyZWN0X3N0cmF0WywgLihhY2N1cmFjeSA9IG5bY29ycmVjdCA9PSAxXS9zdW0obiksIG4gPSBzdW0obikpLCBieSA9IC4oc2Nob29sX3llYXIsIGRveV9wb3NpeCwgY291cnNlLCBsZXZlbCwgeWVhciwgdXNlciwgbWNxKV0KYGBgCgpBbGlnbiBzY2hvb2wgeWVhcnM6CmBgYHtyfQphY2N1cmFjeV9zdHJhdFtzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KYWNjdXJhY3lfc3RyYXRbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKClVzZSBjdXQuRGF0ZSgpIHRvIGJpbiBkYXRlcyBieSB3ZWVrLiBFYWNoIGRheSBpcyBhc3NpZ25lZCB0aGUgZGF0ZSBvZiB0aGUgbW9zdCByZWNlbnQgTW9uZGF5LgpgYGB7cn0KYWNjdXJhY3lfc3RyYXRbLCBkb3lfcG9zaXhfd2VlayA6PSBjdXQuUE9TSVh0KGRveV9wb3NpeCwgIndlZWsiKV0KYWNjdXJhY3lfc3RyYXRbLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJ3ZWVrIildCmBgYAoKYGBge3J9CmFjY3VyYWN5X3N0cmF0X2J5X3dlZWtfYW5kX3VzZXIgPC0gYWNjdXJhY3lfc3RyYXRbLCAuKGFjY3VyYWN5ID0gc3VtKGFjY3VyYWN5Km4pL3N1bShuKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBsZXZlbCwgeWVhciwgdXNlciwgbWNxKV0KYWNjdXJhY3lfc3RyYXRfYnlfd2VlayA8LSBhY2N1cmFjeV9zdHJhdF9ieV93ZWVrX2FuZF91c2VyWywgLihhY2N1cmFjeV9tZWFuID0gbWVhbihhY2N1cmFjeSwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWNjdXJhY3lfc2UgPSBzZChhY2N1cmFjeSwgbmEucm0gPSBUUlVFKS9zcXJ0KC5OKSwgbiA9IC5OKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIsIGRveV9wb3NpeF9hbGlnbmVkX3dlZWssIGxldmVsLCB5ZWFyLCBtY3EpXQpgYGAKCkFkZCBxdWVzdGlvbiB0eXBlIGxhYmVsczoKYGBge3J9CmFjY3VyYWN5X3N0cmF0X2J5X3dlZWtfYW5kX3VzZXJbLCBxdWVzdGlvbl90eXBlIDo9IGlmZWxzZShtY3EgPT0gVFJVRSwgIk11bHRpcGxlXG5jaG9pY2UiLCAiT3BlblxuYW5zd2VyIildCmFjY3VyYWN5X3N0cmF0X2J5X3dlZWtbLCBxdWVzdGlvbl90eXBlIDo9IGlmZWxzZShtY3EgPT0gVFJVRSwgIk11bHRpcGxlXG5jaG9pY2UiLCAiT3BlblxuYW5zd2VyIildCmBgYAoKSG93IG1hbnkgdW5pcXVlIHVzZXJzIHBlciBncm91cD8KYGBge3J9CmFjY3VyYWN5X3N0cmF0X2J5X3dlZWtfYW5kX3VzZXJbLCAuKHVuaXF1ZV91c2VycyA9IGxlbmd0aCh1bmlxdWUodXNlcikpKSwgIGJ5ID0gLihjb3Vyc2UsIGxldmVsLCB5ZWFyLCBzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSldCmBgYAoKClBsb3QgcmVzcG9uc2UgYWNjdXJhY3kgYnkgd2VlayAobWVhbiArLy0gMSBTRSkuCmBgYHtyfQpwX2FjY19sZXZlbF95ZWFyIDwtIGdncGxvdChhY2N1cmFjeV9zdHJhdF9ieV93ZWVrW2NvdXJzZSA9PSAiRnJlbmNoIiAmIGxldmVsICE9ICJPdGhlciIsXSwKICAgICAgICAgICAgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gYWNjdXJhY3lfbWVhbiwgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IHllYXIpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gMCwgeW1heCA9IDEuMDUsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIsIGFscGhhID0gLjkpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IGFjY3VyYWN5X21lYW4gLSBhY2N1cmFjeV9zZSwgeW1heCA9IGFjY3VyYWN5X21lYW4gKyBhY2N1cmFjeV9zZSwgY29sb3VyID0gTlVMTCksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9saW5lKGFlcyhsdHkgPSBxdWVzdGlvbl90eXBlKSkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLjQsIDEpKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkFjY3VyYWN5IiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGx0eSA9ICJRdWVzdGlvbiB0eXBlIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgZmlsbCA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBsdHkgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAyKSkgKwogIHRoZW1lX3BhcGVyCgpwX2FjY19sZXZlbF95ZWFyCgpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV9mcmVuY2hfbGV2ZWxfeWVhci5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L2FjY19ieV9xdWVzdGlvbl90eXBlX2ZyZW5jaF9sZXZlbF95ZWFyLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKYGBgCgpgYGB7cn0KcF9hY2NfbGV2ZWxfeWVhciA8LSBnZ3Bsb3QoYWNjdXJhY3lfc3RyYXRfYnlfd2Vla1tjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHF1ZXN0aW9uX3R5cGUgPT0gIk11bHRpcGxlXG5jaG9pY2UiLF0sCiAgICAgICAgICAgIGFlcyh4ID0gYXMuUE9TSVhjdChkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKSwgeSA9IGFjY3VyYWN5X21lYW4sIGdyb3VwID0gaW50ZXJhY3Rpb24oc2Nob29sX3llYXIsIHF1ZXN0aW9uX3R5cGUpLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiB5ZWFyKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IDAsIHltYXggPSAxLjA1LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyLCBhbHBoYSA9IC45KSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBhY2N1cmFjeV9tZWFuIC0gYWNjdXJhY3lfc2UsIHltYXggPSBhY2N1cmFjeV9tZWFuICsgYWNjdXJhY3lfc2UsIGNvbG91ciA9IE5VTEwpLCBhbHBoYSA9IDAuMikgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKC40LCAxKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJBY2N1cmFjeSIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBsdHkgPSAiUXVlc3Rpb24gdHlwZSIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgbHR5ID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMikpICsKICB0aGVtZV9wYXBlcgoKcF9hY2NfbGV2ZWxfeWVhcgoKZ2dzYXZlKCIuLi9vdXRwdXQvYWNjX2J5X3F1ZXN0aW9uX3R5cGVfZW5nbGlzaF9sZXZlbF95ZWFyLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKZ2dzYXZlKCIuLi9vdXRwdXQvYWNjX2J5X3F1ZXN0aW9uX3R5cGVfZW5nbGlzaF9sZXZlbF95ZWFyLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKYGBgCgojIyMgQnkgbGV2ZWwKCmBgYHtyfQphY2N1cmFjeV9sZXZlbF9ieV93ZWVrX2FuZF91c2VyIDwtIGFjY3VyYWN5X3N0cmF0WywgLihhY2N1cmFjeSA9IHN1bShhY2N1cmFjeSpuKS9zdW0obikpLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfd2VlaywgbGV2ZWwsIHVzZXIsIG1jcSldCgphY2N1cmFjeV9sZXZlbF9ieV93ZWVrIDwtIGFjY3VyYWN5X2xldmVsX2J5X3dlZWtfYW5kX3VzZXJbLCAuKGFjY3VyYWN5X21lYW4gPSBtZWFuKGFjY3VyYWN5LCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY2N1cmFjeV9zZSA9IHNkKGFjY3VyYWN5LCBuYS5ybSA9IFRSVUUpL3NxcnQoLk4pLCBuID0gLk4pLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfd2VlaywgbGV2ZWwsIG1jcSldCmBgYAoKQWRkIHF1ZXN0aW9uIHR5cGUgbGFiZWxzOgpgYGB7cn0KYWNjdXJhY3lfbGV2ZWxfYnlfd2Vla1ssIHF1ZXN0aW9uX3R5cGUgOj0gaWZlbHNlKG1jcSA9PSBUUlVFLCAiTXVsdGlwbGVcbmNob2ljZSIsICJPcGVuXG5hbnN3ZXIiKV0KYGBgCgpIb3cgbWFueSB1c2VycyBpbiBlYWNoIGdyb3VwPwpgYGB7cn0KYWNjdXJhY3lfbGV2ZWxfYnlfd2Vla19hbmRfdXNlclssIC4odW5pcXVlX3VzZXJzID0gbGVuZ3RoKHVuaXF1ZSh1c2VyKSkpLCAgYnkgPSAuKGNvdXJzZSwgbGV2ZWwsIHNjaG9vbF95ZWFyLCBtY3EpXQpgYGAKCgpQbG90IHJlc3BvbnNlIGFjY3VyYWN5IGJ5IHdlZWsgKG1lYW4gKy8tIDEgU0UpLgpgYGB7cn0KcF9hY2NfbGV2ZWwgPC0gZ2dwbG90KGFjY3VyYWN5X2xldmVsX2J5X3dlZWtbKChjb3Vyc2UgPT0gIkVuZ2xpc2giICYgcXVlc3Rpb25fdHlwZSA9PSAiTXVsdGlwbGVcbmNob2ljZSIpIHwgY291cnNlID09ICJGcmVuY2giKSAmIGxldmVsICE9ICJPdGhlciIsXSwKICAgICAgICAgICAgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gYWNjdXJhY3lfbWVhbiwgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IGNvdXJzZSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAwLCB5bWF4ID0gMS4wNSwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMiwgYWxwaGEgPSAuOSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gYWNjdXJhY3lfbWVhbiAtIGFjY3VyYWN5X3NlLCB5bWF4ID0gYWNjdXJhY3lfbWVhbiArIGFjY3VyYWN5X3NlLCBjb2xvdXIgPSBOVUxMKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX2xpbmUoYWVzKGx0eSA9IHF1ZXN0aW9uX3R5cGUpKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYyguNiwgMSkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQWNjdXJhY3kiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiLAogICAgICAgbHR5ID0gIlF1ZXN0aW9uIHR5cGUiKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBmaWxsID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGx0eSA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDIpKSArCiAgdGhlbWVfcGFwZXIKCnBfYWNjX2xldmVsCgpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV9sZXZlbC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L2FjY19ieV9xdWVzdGlvbl90eXBlX2xldmVsLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKYGBgCgoKIyMjIEJ5IHllYXIKCmBgYHtyfQphY2N1cmFjeV95ZWFyX2J5X3dlZWtfYW5kX3VzZXIgPC0gYWNjdXJhY3lfc3RyYXRbLCAuKGFjY3VyYWN5ID0gc3VtKGFjY3VyYWN5Km4pL3N1bShuKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCB5ZWFyLCB1c2VyLCBtY3EpXQoKYWNjdXJhY3lfeWVhcl9ieV93ZWVrIDwtIGFjY3VyYWN5X3llYXJfYnlfd2Vla19hbmRfdXNlclssIC4oYWNjdXJhY3lfbWVhbiA9IG1lYW4oYWNjdXJhY3ksIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY3VyYWN5X3NlID0gc2QoYWNjdXJhY3ksIG5hLnJtID0gVFJVRSkvc3FydCguTiksIG4gPSAuTiksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCB5ZWFyLCBtY3EpXQpgYGAKCkFkZCBxdWVzdGlvbiB0eXBlIGxhYmVsczoKYGBge3J9CmFjY3VyYWN5X3llYXJfYnlfd2Vla1ssIHF1ZXN0aW9uX3R5cGUgOj0gaWZlbHNlKG1jcSA9PSBUUlVFLCAiTXVsdGlwbGVcbmNob2ljZSIsICJPcGVuXG5hbnN3ZXIiKV0KYGBgCgpIb3cgbWFueSB1c2VycyBpbiBlYWNoIGdyb3VwPwpgYGB7cn0KYWNjdXJhY3lfeWVhcl9ieV93ZWVrX2FuZF91c2VyWywgLih1bmlxdWVfdXNlcnMgPSBsZW5ndGgodW5pcXVlKHVzZXIpKSksICBieSA9IC4oY291cnNlLCB5ZWFyLCBzY2hvb2xfeWVhciwgbWNxKV0KYGBgCgoKUGxvdCByZXNwb25zZSBhY2N1cmFjeSBieSB3ZWVrIChtZWFuICsvLSAxIFNFKS4KYGBge3J9CnBfYWNjX3llYXIgPC0gZ2dwbG90KGFjY3VyYWN5X3llYXJfYnlfd2Vla1soKGNvdXJzZSA9PSAiRW5nbGlzaCIgJiBxdWVzdGlvbl90eXBlID09ICJNdWx0aXBsZVxuY2hvaWNlIikgfCBjb3Vyc2UgPT0gIkZyZW5jaCIpICYgeWVhciAhPSAiT3RoZXIiLF0sCiAgICAgICAgICAgIGFlcyh4ID0gYXMuUE9TSVhjdChkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKSwgeSA9IGFjY3VyYWN5X21lYW4sIGdyb3VwID0gaW50ZXJhY3Rpb24oc2Nob29sX3llYXIsIHF1ZXN0aW9uX3R5cGUpLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoeWVhciB+IGNvdXJzZSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAwLCB5bWF4ID0gMS4wNSwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMiwgYWxwaGEgPSAuOSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gYWNjdXJhY3lfbWVhbiAtIGFjY3VyYWN5X3NlLCB5bWF4ID0gYWNjdXJhY3lfbWVhbiArIGFjY3VyYWN5X3NlLCBjb2xvdXIgPSBOVUxMKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX2xpbmUoYWVzKGx0eSA9IHF1ZXN0aW9uX3R5cGUpKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYyguNiwgMSkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQWNjdXJhY3kiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiLAogICAgICAgbHR5ID0gIlF1ZXN0aW9uIHR5cGUiKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBmaWxsID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGx0eSA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDIpKSArCiAgdGhlbWVfcGFwZXIKCnBfYWNjX3llYXIKCmdnc2F2ZSgiLi4vb3V0cHV0L2FjY19ieV9xdWVzdGlvbl90eXBlX3llYXIucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV95ZWFyLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKYGBgCgoKCiMjIyBSZWdyZXNzaW9uIG1vZGVsCgpGaXQgYSBtaXhlZCBlZmZlY3RzIG1vZGVsIHRvIHRoZSBkYWlseSBhY2N1cmFjeSBkYXRhOgpgYGB7cn0KYWNjdXJhY3lbLCBwZXJpb2QgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBkb3lfcG9zaXhfYWxpZ25lZCA+PSBkYXRlX3NjaG9vbHNfb3BlbmVkIH4gInBvc3QtbG9ja2Rvd24iLAogIGRveV9wb3NpeF9hbGlnbmVkID49IGRhdGVfc2Nob29sc19jbG9zZWQgJiBkb3lfcG9zaXhfYWxpZ25lZCA8IGRhdGVfc2Nob29sc19vcGVuZWQgfiAiZHVyaW5nLWxvY2tkb3duIiwKICBkb3lfcG9zaXhfYWxpZ25lZCA8IGRhdGVfc2Nob29sc19vcGVuZWQgfiAicHJlLWxvY2tkb3duIgopXQoKIyBSZW9yZGVyIGZhY3RvciBsZXZlbHMgc28gdGhhdCBpbnRlcmNlcHQgaXMgcHJlLWxvY2tkb3duIG9wZW4gYW5zd2VyIGluIDE5LzIwCmFjY3VyYWN5WywgcGVyaW9kIDo9IGZhY3RvcihwZXJpb2QsIGxldmVscyA9IGMoInByZS1sb2NrZG93biIsICJkdXJpbmctbG9ja2Rvd24iLCAicG9zdC1sb2NrZG93biIpKV0KYWNjdXJhY3lbLCBzY2hvb2xfeWVhciA6PSBmYWN0b3Ioc2Nob29sX3llYXIsIGxldmVscyA9IGMoIjE5LzIwIiwgIjE4LzE5IikpXQphY2N1cmFjeVssIG1jcSA6PSBmYWN0b3IobWNxLCBsZXZlbHMgPSBjKEZBTFNFLCBUUlVFKSldCmBgYAoKU2luY2Ugd2Uga25vdyB0aGUgbnVtYmVyIG9mIHRyaWFscyBwZXIgZGF5IGFuZCB0aGUgcHJvcG9ydGlvbiBjb3JyZWN0IChhY2N1cmFjeSksIHdlIGNhbiB1c2UgYSBiaW5vbWlhbCBHTE1NOgpgYGB7cn0KaWYoIWZpbGUuZXhpc3RzKCIuLi9vdXRwdXQvbV9hY2NfZml0LnJkcyIpKSB7CiAgbV9hY2MgPC0gZ2xtZXIoYWNjdXJhY3kgfiBwZXJpb2Qqc2Nob29sX3llYXIqbWNxICsgKDEgfCB1c2VyKSArICgxIHwgY291cnNlKSwKICAgICAgICAgICAgICAgICBkYXRhID0gYWNjdXJhY3lbKGNvdXJzZSA9PSAiRW5nbGlzaCIgJiBtY3EgPT0gVFJVRSkgfCBjb3Vyc2UgPT0gIkZyZW5jaCIsXSwKICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLCAKICAgICAgICAgICAgICAgICB3ZWlnaHRzID0gbiwKICAgICAgICAgICAgICAgICBuQUdRID0gMCwKICAgICAgICAgICAgICAgICBjb250cm9sID0gZ2xtZXJDb250cm9sKG9wdGltaXplciA9ICJib2J5cWEiLCBvcHRDdHJsID0gbGlzdChtYXhmdW4gPSAxZTYpKSkKICBzYXZlUkRTKG1fYWNjLCAiLi4vb3V0cHV0L21fYWNjX2ZpdC5yZHMiKQp9IGVsc2UgewogIG1fYWNjIDwtIHJlYWRSRFMoIi4uL291dHB1dC9tX2FjY19maXQucmRzIikKfQoKbV9hY2Nfc3VtbWFyeSA8LSBzdW1tYXJ5KG1fYWNjKQptX2FjY19zdW1tYXJ5CmBgYAoKU2F2ZSBjb2VmZmljaWVudHMgYXMgYSB0YWJsZSBmb3IgaW4gdGhlIHBhcGVyOgpgYGB7cn0KbV9hY2NfY29lZiA8LSBhcy5kYXRhLmZyYW1lKG1fYWNjX3N1bW1hcnkkY29lZmZpY2llbnRzKQpzZXREVChtX2FjY19jb2VmLCBrZWVwLnJvd25hbWVzID0gVFJVRSkKbV9hY2NfY29lZiRybiA8LSBjKCJJbnRlcmNlcHQgXFxzbWFsbHsoUGVyaW9kOiBwcmUtbG9ja2Rvd24sIFNjaG9vbCB5ZWFyOiAxOS8yMCwgUXVlc3Rpb24gdHlwZTogb3BlbiBhbnN3ZXIpfSIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBsb2NrZG93biIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBwb3N0LWxvY2tkb3duIiwKICAgICAgICAgICAgICAgICAgICJTY2hvb2wgeWVhcjogMTgvMTkiLAogICAgICAgICAgICAgICAgICAgIlF1ZXN0aW9uIHR5cGU6IG11bHRpcGxlIGNob2ljZSIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBsb2NrZG93biAkXFx0aW1lcyQgU2Nob29sIHllYXI6IDE4LzE5IiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IHBvc3QtbG9ja2Rvd24gJFxcdGltZXMkIFNjaG9vbCB5ZWFyOiAxOC8xOSIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBsb2NrZG93biAkXFx0aW1lcyQgUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IHBvc3QtbG9ja2Rvd24gJFxcdGltZXMkIFF1ZXN0aW9uIHR5cGU6IG11bHRpcGxlIGNob2ljZSIsCiAgICAgICAgICAgICAgICAgICAiU2Nob29sIHllYXI6IDE4LzE5ICRcXHRpbWVzJCBRdWVzdGlvbiB0eXBlOiBtdWx0aXBsZSBjaG9pY2UiLAogICAgICAgICAgICAgICAgICAgIlBlcmlvZDogbG9ja2Rvd24gJFxcdGltZXMkIFNjaG9vbCB5ZWFyOiAxOC8xOSAkXFx0aW1lcyQgUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IHBvc3QtbG9ja2Rvd24gJFxcdGltZXMkIFNjaG9vbCB5ZWFyOiAxOC8xOSAkXFx0aW1lcyQgUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIikKCiMgRm9ybWF0IHAtdmFsdWVzCm1fYWNjX2NvZWYkYFByKD58enwpYCA8LSBmb3JtYXQucHZhbChtX2FjY19jb2VmJGBQcig+fHp8KWAsIGVwcyA9IC4wMDEsIGRpZ2l0cyA9IDMsIGZsYWcgPSAiMCIpCm1fYWNjX2NvZWYkYFByKD58enwpYCA8LSBzdWIoJ14oPCk/MFsuXScsICdcXDEuJywgbV9hY2NfY29lZiRgUHIoPnx6fClgKSAjIFJlbW92ZSBsZWFkaW5nIHplcm8KCmNhdChrbml0cjo6a2FibGUobV9hY2NfY29lZiwKICAgICAgICAgICAgICAgICBhbGlnbiA9IGMoImwiLCJyIiwgInIiLCAiciIsICJyIiksCiAgICAgICAgICAgICAgICAgZGlnaXRzID0gYyhOQSwgMywgMywgMiwgTkEpLAogICAgICAgICAgICAgICAgIGNvbC5uYW1lcyA9IGMoIkVmZmVjdCIsICIkYiQiLCAiU0UiLCAiJHokIiwgIiRwJCIpLAogICAgICAgICAgICAgICAgIGZvcm1hdCA9ICJsYXRleCIsCiAgICAgICAgICAgICAgICAgYm9va3RhYnMgPSBUUlVFLAogICAgICAgICAgICAgICAgIGVzY2FwZSA9IEZBTFNFKSwKICAgIGZpbGUgPSAiLi4vb3V0cHV0L21fYWNjX3RhYmxlLnRleCIpCmBgYAoKVmlzdWFsaXNlIHRoZSBtb2RlbCBmaXQ6CmBgYHtyfQphY2NfZml0IDwtIGV4cGFuZC5ncmlkKHBlcmlvZCA9IGMoInByZS1sb2NrZG93biIsICJkdXJpbmctbG9ja2Rvd24iLCAicG9zdC1sb2NrZG93biIpLCBzY2hvb2xfeWVhciA9IGMoIjE4LzE5IiwgIjE5LzIwIiksIG1jcSA9IGMoVFJVRSwgRkFMU0UpKQphY2NfZml0IDwtIGNiaW5kKGFjY19maXQsIGFjY3VyYWN5ID0gcHJlZGljdChtX2FjYywgdHlwZSA9ICJyZXNwb25zZSIsIHJlLmZvcm0gPSBOQSwgbmV3ZGF0YSA9IGFjY19maXQpKQphY2NfZml0CgpnZ3Bsb3QoYWNjX2ZpdCwgYWVzKHggPSBwZXJpb2QsIHkgPSBhY2N1cmFjeSwgY29sb3VyID0gc2Nob29sX3llYXIsIGx0eSA9IG1jcSwgZ3JvdXAgPSBpbnRlcmFjdGlvbihtY3EsIHNjaG9vbF95ZWFyKSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYyguNywgMSksIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSkgKwogIHRoZW1lX3BhcGVyCmBgYAoKRW1waXJpY2FsIG1lYW5zOgpgYGB7cn0KYWNjdXJhY3lfbWVhbiA8LSBhY2N1cmFjeVsoY291cnNlID09ICJFbmdsaXNoIiAmIG1jcSA9PSBUUlVFKSB8IGNvdXJzZSA9PSAiRnJlbmNoIiwgLihhY2N1cmFjeSA9IHN1bShhY2N1cmFjeSAqIG4pL3N1bShuKSksIGJ5ID0gLihwZXJpb2QsIHNjaG9vbF95ZWFyLCBtY3EsIHVzZXIsIGNvdXJzZSldWywgLihhY2N1cmFjeSA9IG1lYW4oYWNjdXJhY3kpLCBhY2N1cmFjeV9zZCA9IHNkKGFjY3VyYWN5KSksIGJ5ID0gLihwZXJpb2QsIHNjaG9vbF95ZWFyLCBtY3EpXQphY2N1cmFjeV9tZWFuCmBgYAoKYGBge3J9CmdncGxvdChhY2N1cmFjeV9tZWFuLCBhZXMoeCA9IHBlcmlvZCwgeSA9IGFjY3VyYWN5LCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgbHR5ID0gbWNxLCBncm91cCA9IGludGVyYWN0aW9uKG1jcSwgc2Nob29sX3llYXIpKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKC43LCAxKSwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArCiAgdGhlbWVfcGFwZXIKYGBgCgoKCgojIyBSZXNwb25zZSB0aW1lCgpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCnJ0IDwtIGRiR2V0UXVlcnkoZGIsIAogICAgICAgICAgICAgICAgICJTRUxFQ1Qgci5tZXRob2QgQVMgJ21ldGhvZCcsCiAgICAgICAgICAgICAgICAgIHIuYm9va19pbmZvX2lkIEFTICdib29rX2luZm9faWQnLAogICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSBBUyAnZG95JywKICAgICAgICAgICAgICAgICAgci51c2VyX2lkIEFTICd1c2VyJywKICAgICAgICAgICAgICAgICAgci5jaG9pY2VzID4gMSBBUyAnbWNxJywKICAgICAgICAgICAgICAgICAgci5ydCBBUyAncnQnCiAgICAgICAgICAgICAgICAgIEZST00gJ3Jlc3BvbnNlcycgcgogICAgICAgICAgICAgICAgICBXSEVSRSByLnN0dWR5ID09IDAKICAgICAgICAgICAgICAgICAgQU5EIHIuY29ycmVjdCA9PSAxCiAgICAgICAgICAgICAgICAgIgopCnNldERUKHJ0KQpkYl9kaXNjb25uZWN0KGRiKQpgYGAKCmBgYHtyfQpkb3lzIDwtIHJ0WywgLihkb3kgPSB1bmlxdWUoZG95KSldWywgZG95X3Bvc2l4IDo9IGFzLlBPU0lYY3QoZG95KV1bXQpkb3lzWywgZG95X3Bvc2l4X3dlZWsgOj0gY3V0LlBPU0lYdChhcy5QT1NJWGN0KGRveSksICJ3ZWVrIildCmRveXNbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KZG95c1tzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KZG95c1tzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBkb3lfcG9zaXhdCmRveXNbLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJ3ZWVrIildCmRveXNbLCBwZXJpb2QgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBkb3lfcG9zaXhfYWxpZ25lZCA+PSBkYXRlX3NjaG9vbHNfb3BlbmVkIH4gInBvc3QtbG9ja2Rvd24iLAogIGRveV9wb3NpeF9hbGlnbmVkID49IGRhdGVfc2Nob29sc19jbG9zZWQgJiBkb3lfcG9zaXhfYWxpZ25lZCA8IGRhdGVfc2Nob29sc19vcGVuZWQgfiAiZHVyaW5nLWxvY2tkb3duIiwKICBkb3lfcG9zaXhfYWxpZ25lZCA8IGRhdGVfc2Nob29sc19vcGVuZWQgfiAicHJlLWxvY2tkb3duIgopXQoKIyBSZW9yZGVyIGZhY3RvciBsZXZlbHMgc28gdGhhdCBpbnRlcmNlcHQgaXMgcHJlLWxvY2tkb3duIGluIDE5LzIwCmRveXNbLCBwZXJpb2QgOj0gZmFjdG9yKHBlcmlvZCwgbGV2ZWxzID0gYygicHJlLWxvY2tkb3duIiwgImR1cmluZy1sb2NrZG93biIsICJwb3N0LWxvY2tkb3duIikpXQpkb3lzWywgc2Nob29sX3llYXIgOj0gZmFjdG9yKHNjaG9vbF95ZWFyLCBsZXZlbHMgPSBjKCIxOS8yMCIsICIxOC8xOSIpKV0KYGBgCgpgYGB7cn0KcnQgPC0gcnRbZG95cywgb24gPSAiZG95Il0KYGBgCgpgYGB7cn0KcnRbLCBtY3EgOj0gYXMuZmFjdG9yKGFzLmxvZ2ljYWwobWNxKSldCmBgYAoKYGBge3J9CnJ0WywgY291cnNlIDo9IGlmZWxzZShtZXRob2QgPT0gIkdyYW5kZXMgTGlnbmVzIiwgIkZyZW5jaCIsIGlmZWxzZShtZXRob2QgPT0gIlN0ZXBwaW5nIFN0b25lcyIsICJFbmdsaXNoIiwgIkdlcm1hbiIpKV0KYGBgCgpUaHJvdyBvdXQgdHJpYWxzIHdpdGggbmVnYXRpdmUgUlRzICh0aW1pbmcgZXJyb3JzKQpgYGB7cn0KcnQgPC0gcnRbcnQgPiAwXQpgYGAKCgoKIyMjIFdob2xlIHBvcHVsYXRpb24KCmBgYHtyfQpydF9tZWQgPC0gcnRbLCAuKHJ0X21lZGlhbiA9IG1lZGlhbihydCkpLCBieSA9IC4oc2Nob29sX3llYXIsIG1jcSwgdXNlciwgY291cnNlLCBkb3lfcG9zaXhfd2VlayldCgpydF9ieV93ZWVrIDwtIHJ0X21lZFssIC4ocnQgPSBtZWFuKHJ0X21lZGlhbiksIHJ0X3NlID0gc2QocnRfbWVkaWFuKS9zcXJ0KC5OKSksIGJ5ID0gLihzY2hvb2xfeWVhciwgbWNxLCBjb3Vyc2UsIGRveV9wb3NpeF93ZWVrKV0KYGBgCgpPdmVybGFwIHRoZSB0d28gc2Nob29sIHllYXJzOgpgYGB7cn0KcnRfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfd2Vla19hbGlnbmVkIDo9IGFzLlBPU0lYY3QoYXMuUE9TSVhjdChkb3lfcG9zaXhfd2VlaykgKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCnJ0X2J5X3dlZWtbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X3dlZWtfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeF93ZWVrKV0KYGBgCgpBZGQgcXVlc3Rpb24gdHlwZSBsYWJlbHM6CmBgYHtyfQpydF9ieV93ZWVrWywgcXVlc3Rpb25fdHlwZSA6PSBpZmVsc2UobWNxID09IFRSVUUsICJNdWx0aXBsZVxuY2hvaWNlIiwgIk9wZW5cbmFuc3dlciIpXQpgYGAKCmBgYHtyfQpydF9ieV93ZWVrWywgc2Nob29sX3llYXIgOj0gZmFjdG9yKHNjaG9vbF95ZWFyLCBsZXZlbHMgPSBjKCIxOC8xOSIsICIxOS8yMCIpKV0KYGBgCgoKUGxvdCByZXNwb25zZSB0aW1lIGJ5IHdlZWsgKG1lYW4gKy8tIDEgU0UpLgpgYGB7cn0KcF9ydCA8LSBnZ3Bsb3QocnRfYnlfd2Vla1soY291cnNlID09ICJFbmdsaXNoIiAmIG1jcSA9PSBUUlVFKSB8IGNvdXJzZSA9PSAiRnJlbmNoIixdLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF93ZWVrX2FsaWduZWQsIHkgPSBydC8xZTMsIGdyb3VwID0gaW50ZXJhY3Rpb24oc2Nob29sX3llYXIsIHF1ZXN0aW9uX3R5cGUpLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoLiB+IGNvdXJzZSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAwLCB5bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMiwgYWxwaGEgPSAuOSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcnQvMWUzIC0gcnRfc2UvMWUzLCB5bWF4ID0gcnQvMWUzICsgcnRfc2UvMWUzLCBjb2xvdXIgPSBOVUxMKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX2xpbmUoYWVzKGx0eSA9IHF1ZXN0aW9uX3R5cGUpKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICJzIiwgYWNjdXJhY3kgPSAuMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMS43LCAzLjcpKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlJlc3BvbnNlIHRpbWUiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiLAogICAgICAgbHR5ID0gIlF1ZXN0aW9uIHR5cGUiKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBmaWxsID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGx0eSA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDIpKSArCiAgdGhlbWVfcGFwZXIKCnBfcnQKCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGUucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzKQpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKYGBgCgojIyMgQnkgbGV2ZWwgYW5kIHllYXIKCkFkZCBib29rIGluZm86CmBgYHtyfQpydFtib29rX2luZm9bYm9va190eXBlID09ICJIb29mZGJvZWsiLF0sIG9uICA9ICJib29rX2luZm9faWQiLCBjKCJtZXRob2RfZ3JvdXAiLCAiYm9va190aXRsZSIpIDo9IC4oaS5tZXRob2RfZ3JvdXAsIGkuYm9va190aXRsZSldCmBgYAoKU2ltcGxpZnkgbGV2ZWwgbmFtZXM6CmBgYHtyfQojIEtlZXAgYWxsIGRpc3RpbmN0aW9ucwpydFssIGJvb2tfdGl0bGVfc2ltcGxlIDo9IHN0cmluZ3I6OnN0cl9zdWIoYm9va190aXRsZSwgMywgLTEwKV0KcnRbLCBib29rX3RpdGxlX3NpbXBsZSA6PSBmYWN0b3IoYm9va190aXRsZV9zaW1wbGUsIGxldmVscyA9IGMoInZtYm8gYi9sd29vIiwgInZtYm8gYiIsICJ2bWJvIGJrIiwgInZtYm8gayIsICJ2bWJvIGtndCIsICJ2bWJvLWd0IiwgInZtYm8gZ3QiLCAidm1iby1ndC9oYXZvIiwgInZtYm8gKHQpaHYiLCAiaGF2byIsICJoYXZvIHZ3byIsICJ2d28iKSldCgojIFNpbXBsaWZ5IHRvIHRocmVlIGxldmVscwpydFssIGxldmVsIDo9IGRwbHlyOjpjYXNlX3doZW4oCiAgZ3JlcGwoICJodiIsIGJvb2tfdGl0bGUpIH4gIkdlbmVyYWwgc2Vjb25kYXJ5XG4oaGF2bykiLAogIGdyZXBsKCJ2bWJvIiwgYm9va190aXRsZSkgfiAiUHJlLXZvY2F0aW9uYWxcbih2bWJvKSIsCiAgZ3JlcGwoImhhdm8iLCBib29rX3RpdGxlKSB+ICJHZW5lcmFsIHNlY29uZGFyeVxuKGhhdm8pIiwKICBncmVwbCgidndvIiwgYm9va190aXRsZSkgfiAiUHJlLXVuaXZlcnNpdHlcbih2d28pIiwKICBUUlVFIH4gIk90aGVyIildCnJ0WywgbGV2ZWwgOj0gZmFjdG9yKGxldmVsLCBsZXZlbHMgPSBjKCJPdGhlciIsICJQcmUtdm9jYXRpb25hbFxuKHZtYm8pIiwgIkdlbmVyYWwgc2Vjb25kYXJ5XG4oaGF2bykiLCAiUHJlLXVuaXZlcnNpdHlcbih2d28pIikpXQpgYGAKClNpbXBsaWZ5IHllYXIgbmFtZXM6CmBgYHtyfQpydFssIHllYXIgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDEgKDVlIEVkLikiIH4gIlllYXIgMSIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAyICg1ZSBFZC4pIiB+ICJZZWFyIDIiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMyAoNWUgRWQuKSIgfiAiWWVhciAzIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDMvNCAoNWUgRWQuKSIgfiAiWWVhciAzLzQiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgNCAoNWUgRWQuKSIgfiAiWWVhciA0IiwKICBtZXRob2RfZ3JvdXAgPT0gIlR3ZWVkZSBGYXNlICg2ZSBFZC4pIiB+ICJUd2VlZGUgRmFzZSIsCiAgVFJVRSB+ICJPdGhlciIpXQpgYGAKCgpgYGB7cn0KcnRfc3RyYXRfbWVkIDwtIHJ0WywgLihydF9tZWRpYW4gPSBtZWRpYW4ocnQpKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBtY3EsIHVzZXIsIGNvdXJzZSwgbGV2ZWwsIHllYXIsIGRveV9wb3NpeF93ZWVrKV0KCnJ0X3N0cmF0X2J5X3dlZWsgPC0gcnRfc3RyYXRfbWVkWywgLihydCA9IG1lYW4ocnRfbWVkaWFuKSwgcnRfc2UgPSBzZChydF9tZWRpYW4pL3NxcnQoLk4pKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBtY3EsIGNvdXJzZSwgbGV2ZWwsIHllYXIsIGRveV9wb3NpeF93ZWVrKV0KYGBgCgpPdmVybGFwIHRoZSB0d28gc2Nob29sIHllYXJzOgpgYGB7cn0KcnRfc3RyYXRfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfd2Vla19hbGlnbmVkIDo9IGFzLlBPU0lYY3QoYXMuUE9TSVhjdChkb3lfcG9zaXhfd2VlaykgKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCnJ0X3N0cmF0X2J5X3dlZWtbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X3dlZWtfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeF93ZWVrKV0KYGBgCgpBZGQgcXVlc3Rpb24gdHlwZSBsYWJlbHM6CmBgYHtyfQpydF9zdHJhdF9ieV93ZWVrWywgcXVlc3Rpb25fdHlwZSA6PSBpZmVsc2UobWNxID09IFRSVUUsICJNdWx0aXBsZVxuY2hvaWNlIiwgIk9wZW5cbmFuc3dlciIpXQpgYGAKCmBgYHtyfQpydF9zdHJhdF9ieV93ZWVrWywgc2Nob29sX3llYXIgOj0gZmFjdG9yKHNjaG9vbF95ZWFyLCBsZXZlbHMgPSBjKCIxOC8xOSIsICIxOS8yMCIpKV0KYGBgCgoKUGxvdCByZXNwb25zZSB0aW1lIGJ5IHdlZWsgKG1lYW4gKy8tIDEgU0UpLgpgYGB7cn0KcF9ydF9sZXZlbF95ZWFyIDwtIGdncGxvdChydF9zdHJhdF9ieV93ZWVrW2NvdXJzZSA9PSAiRnJlbmNoIixdLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF93ZWVrX2FsaWduZWQsIHkgPSBydC8xZTMsIGdyb3VwID0gaW50ZXJhY3Rpb24oc2Nob29sX3llYXIsIHF1ZXN0aW9uX3R5cGUpLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiB5ZWFyKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IDAsIHltYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyLCBhbHBoYSA9IC45KSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBydC8xZTMgLSBydF9zZS8xZTMsIHltYXggPSBydC8xZTMgKyBydF9zZS8xZTMsIGNvbG91ciA9IE5VTEwpLCBhbHBoYSA9IDAuMikgKwogIGdlb21fbGluZShhZXMobHR5ID0gcXVlc3Rpb25fdHlwZSkpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gInMiLCBhY2N1cmFjeSA9IC4xKSkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygxLCA0KSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJSZXNwb25zZSB0aW1lIiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGx0eSA9ICJRdWVzdGlvbiB0eXBlIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgZmlsbCA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBsdHkgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAyKSkgKwogIHRoZW1lX3BhcGVyCgpwX3J0X2xldmVsX3llYXIKCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfZnJlbmNoX2xldmVsX3llYXIucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzKQpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlX2ZyZW5jaF9sZXZlbF95ZWFyLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKYGBgCgpgYGB7cn0KcF9ydF9sZXZlbF95ZWFyIDwtIGdncGxvdChydF9zdHJhdF9ieV93ZWVrW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiBxdWVzdGlvbl90eXBlID09ICJNdWx0aXBsZVxuY2hvaWNlIiAmIGxldmVsICE9ICJPdGhlciIsXSwKICAgICAgICAgICAgYWVzKHggPSBkb3lfcG9zaXhfd2Vla19hbGlnbmVkLCB5ID0gcnQvMWUzLCBncm91cCA9IGludGVyYWN0aW9uKHNjaG9vbF95ZWFyLCBxdWVzdGlvbl90eXBlKSwgY29sb3VyID0gc2Nob29sX3llYXIsIGZpbGwgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKGxldmVsIH4geWVhcikgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAwLCB5bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMiwgYWxwaGEgPSAuOSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcnQvMWUzIC0gcnRfc2UvMWUzLCB5bWF4ID0gcnQvMWUzICsgcnRfc2UvMWUzLCBjb2xvdXIgPSBOVUxMKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX2xpbmUoYWVzKGx0eSA9IHF1ZXN0aW9uX3R5cGUpKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICJzIiwgYWNjdXJhY3kgPSAuMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMSwgNCkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiUmVzcG9uc2UgdGltZSIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBsdHkgPSAiUXVlc3Rpb24gdHlwZSIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgbHR5ID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMikpICsKICB0aGVtZV9wYXBlcgoKcF9ydF9sZXZlbF95ZWFyCgpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlX2VuZ2xpc2hfbGV2ZWxfeWVhci5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfZW5nbGlzaF9sZXZlbF95ZWFyLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKYGBgCgojIyMgQnkgbGV2ZWwKCmBgYHtyfQpydF9sZXZlbF9tZWQgPC0gcnRbLCAuKHJ0X21lZGlhbiA9IG1lZGlhbihydCkpLCBieSA9IC4oc2Nob29sX3llYXIsIG1jcSwgdXNlciwgY291cnNlLCBsZXZlbCwgZG95X3Bvc2l4X3dlZWspXQoKcnRfbGV2ZWxfYnlfd2VlayA8LSBydF9sZXZlbF9tZWRbLCAuKHJ0ID0gbWVhbihydF9tZWRpYW4pLCBydF9zZSA9IHNkKHJ0X21lZGlhbikvc3FydCguTikpLCBieSA9IC4oc2Nob29sX3llYXIsIG1jcSwgY291cnNlLCBsZXZlbCwgZG95X3Bvc2l4X3dlZWspXQpgYGAKCk92ZXJsYXAgdGhlIHR3byBzY2hvb2wgeWVhcnM6CmBgYHtyfQpydF9sZXZlbF9ieV93ZWVrW3NjaG9vbF95ZWFyID09ICIxOC8xOSIsIGRveV9wb3NpeF93ZWVrX2FsaWduZWQgOj0gYXMuUE9TSVhjdChhcy5QT1NJWGN0KGRveV9wb3NpeF93ZWVrKSArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KcnRfbGV2ZWxfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfd2Vla19hbGlnbmVkIDo9IGFzLlBPU0lYY3QoZG95X3Bvc2l4X3dlZWspXQpgYGAKCkFkZCBxdWVzdGlvbiB0eXBlIGxhYmVsczoKYGBge3J9CnJ0X2xldmVsX2J5X3dlZWtbLCBxdWVzdGlvbl90eXBlIDo9IGlmZWxzZShtY3EgPT0gVFJVRSwgIk11bHRpcGxlXG5jaG9pY2UiLCAiT3BlblxuYW5zd2VyIildCmBgYAoKYGBge3J9CnJ0X2xldmVsX2J5X3dlZWtbLCBzY2hvb2xfeWVhciA6PSBmYWN0b3Ioc2Nob29sX3llYXIsIGxldmVscyA9IGMoIjE4LzE5IiwgIjE5LzIwIikpXQpgYGAKCgpQbG90IHJlc3BvbnNlIHRpbWUgYnkgd2VlayAobWVhbiArLy0gMSBTRSkuCmBgYHtyfQpwX3J0X2xldmVsIDwtIGdncGxvdChydF9sZXZlbF9ieV93ZWVrWygoY291cnNlID09ICJFbmdsaXNoIiAmIHF1ZXN0aW9uX3R5cGUgPT0gIk11bHRpcGxlXG5jaG9pY2UiKSB8IGNvdXJzZSA9PSAiRnJlbmNoIikgJiBsZXZlbCAhPSAiT3RoZXIiLF0sCiAgICAgICAgICAgIGFlcyh4ID0gZG95X3Bvc2l4X3dlZWtfYWxpZ25lZCwgeSA9IHJ0LzFlMywgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IGNvdXJzZSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAwLCB5bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMiwgYWxwaGEgPSAuOSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcnQvMWUzIC0gcnRfc2UvMWUzLCB5bWF4ID0gcnQvMWUzICsgcnRfc2UvMWUzLCBjb2xvdXIgPSBOVUxMKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX2xpbmUoYWVzKGx0eSA9IHF1ZXN0aW9uX3R5cGUpKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICJzIiwgYWNjdXJhY3kgPSAuMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMSwgNikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiUmVzcG9uc2UgdGltZSIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBsdHkgPSAiUXVlc3Rpb24gdHlwZSIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgbHR5ID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMikpICsKICB0aGVtZV9wYXBlcgoKcF9ydF9sZXZlbAoKZ2dzYXZlKCIuLi9vdXRwdXQvcnRfYnlfcXVlc3Rpb25fdHlwZV9sZXZlbC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfbGV2ZWwucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpgYGAKCiMjIyBCeSB5ZWFyCgpgYGB7cn0KcnRfeWVhcl9tZWQgPC0gcnRbLCAuKHJ0X21lZGlhbiA9IG1lZGlhbihydCkpLCBieSA9IC4oc2Nob29sX3llYXIsIG1jcSwgdXNlciwgY291cnNlLCB5ZWFyLCBkb3lfcG9zaXhfd2VlayldCgpydF95ZWFyX2J5X3dlZWsgPC0gcnRfeWVhcl9tZWRbLCAuKHJ0ID0gbWVhbihydF9tZWRpYW4pLCBydF9zZSA9IHNkKHJ0X21lZGlhbikvc3FydCguTikpLCBieSA9IC4oc2Nob29sX3llYXIsIG1jcSwgY291cnNlLCB5ZWFyLCBkb3lfcG9zaXhfd2VlayldCmBgYAoKT3ZlcmxhcCB0aGUgdHdvIHNjaG9vbCB5ZWFyczoKYGBge3J9CnJ0X3llYXJfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfd2Vla19hbGlnbmVkIDo9IGFzLlBPU0lYY3QoYXMuUE9TSVhjdChkb3lfcG9zaXhfd2VlaykgKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCnJ0X3llYXJfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfd2Vla19hbGlnbmVkIDo9IGFzLlBPU0lYY3QoZG95X3Bvc2l4X3dlZWspXQpgYGAKCkFkZCBxdWVzdGlvbiB0eXBlIGxhYmVsczoKYGBge3J9CnJ0X3llYXJfYnlfd2Vla1ssIHF1ZXN0aW9uX3R5cGUgOj0gaWZlbHNlKG1jcSA9PSBUUlVFLCAiTXVsdGlwbGVcbmNob2ljZSIsICJPcGVuXG5hbnN3ZXIiKV0KYGBgCgpgYGB7cn0KcnRfeWVhcl9ieV93ZWVrWywgc2Nob29sX3llYXIgOj0gZmFjdG9yKHNjaG9vbF95ZWFyLCBsZXZlbHMgPSBjKCIxOC8xOSIsICIxOS8yMCIpKV0KYGBgCgoKUGxvdCByZXNwb25zZSB0aW1lIGJ5IHdlZWsgKG1lYW4gKy8tIDEgU0UpLgpgYGB7cn0KcF9ydF95ZWFyIDwtIGdncGxvdChydF95ZWFyX2J5X3dlZWtbKChjb3Vyc2UgPT0gIkVuZ2xpc2giICYgcXVlc3Rpb25fdHlwZSA9PSAiTXVsdGlwbGVcbmNob2ljZSIpIHwgY291cnNlID09ICJGcmVuY2giKSAmIHllYXIgIT0gIk90aGVyIixdLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF93ZWVrX2FsaWduZWQsIHkgPSBydC8xZTMsIGdyb3VwID0gaW50ZXJhY3Rpb24oc2Nob29sX3llYXIsIHF1ZXN0aW9uX3R5cGUpLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoeWVhciB+IGNvdXJzZSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAwLCB5bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMiwgYWxwaGEgPSAuOSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcnQvMWUzIC0gcnRfc2UvMWUzLCB5bWF4ID0gcnQvMWUzICsgcnRfc2UvMWUzLCBjb2xvdXIgPSBOVUxMKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX2xpbmUoYWVzKGx0eSA9IHF1ZXN0aW9uX3R5cGUpKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICJzIiwgYWNjdXJhY3kgPSAuMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMSwgNCkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiUmVzcG9uc2UgdGltZSIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBsdHkgPSAiUXVlc3Rpb24gdHlwZSIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgbHR5ID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMikpICsKICB0aGVtZV9wYXBlcgoKcF9ydF95ZWFyCgpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlX3llYXIucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlX3llYXIucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpgYGAKCgojIyMgUmVncmVzc2lvbiBtb2RlbAoKYGBge3J9CnJ0X21vZGVsX2RhdCA8LSBydFssIC4ocnRfbWVkaWFuID0gbWVkaWFuKHJ0KSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBwZXJpb2QsIGRveV9wb3NpeCwgbWNxLCB1c2VyKV0KYGBgCgpGaXQgYSBnZW5lcmFsaXNlZCBsaW5lYXIgbWl4ZWQgZWZmZWN0cyBtb2RlbCAoYXNzdW1pbmcgYSBHYW1tYSBkaXN0cmlidXRpb24gZm9yIFJUIGFuZCBhbiBpZGVudGl0eSBsaW5rIGZ1bmN0aW9uOyBMbyAmIEFuZHJldywgMjAxNSkgdG8gdGhlIGRhaWx5IG1lZGlhbiBSVDoKYGBge3J9CmlmKCFmaWxlLmV4aXN0cygiLi4vb3V0cHV0L21fcnRfZml0LnJkcyIpKSB7CiAgbV9ydCA8LSBnbG1lcihydF9tZWRpYW4gfiBwZXJpb2Qqc2Nob29sX3llYXIqbWNxICsgKDEgfCB1c2VyKSArICgxIHwgY291cnNlKSwKICAgICAgICAgICAgICAgICBkYXRhID0gcnRfbW9kZWxfZGF0Wyhjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbWNxID09IFRSVUUpIHwgY291cnNlID09ICJGcmVuY2giLF0sCiAgICAgICAgICAgICAgICAgZmFtaWx5ID0gR2FtbWEobGluayA9ICJpZGVudGl0eSIpLAogICAgICAgICAgICAgICAgIG5BR1EgPSAwLAogICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBnbG1lckNvbnRyb2wob3B0aW1pemVyID0gImJvYnlxYSIsIG9wdEN0cmwgPSBsaXN0KG1heGZ1biA9IDFlNikpKQogIHNhdmVSRFMobV9ydCwgIi4uL291dHB1dC9tX3J0X2ZpdC5yZHMiKQp9IGVsc2UgewogIG1fcnQgPC0gcmVhZFJEUygiLi4vb3V0cHV0L21fcnRfZml0LnJkcyIpCn0KCm1fcnRfc3VtbWFyeSA8LSBzdW1tYXJ5KG1fcnQpCm1fcnRfc3VtbWFyeQpgYGAKClNhdmUgY29lZmZpY2llbnRzIGFzIGEgdGFibGUgZm9yIGluIHRoZSBwYXBlcjoKYGBge3J9Cm1fcnRfY29lZiA8LSBhcy5kYXRhLmZyYW1lKG1fcnRfc3VtbWFyeSRjb2VmZmljaWVudHMpCnNldERUKG1fcnRfY29lZiwga2VlcC5yb3duYW1lcyA9IFRSVUUpCm1fcnRfY29lZiRybiA8LSBjKCJJbnRlcmNlcHQgXFxzbWFsbHsoUGVyaW9kOiBwcmUtbG9ja2Rvd24sIFNjaG9vbCB5ZWFyOiAxOS8yMCwgUXVlc3Rpb24gdHlwZTogb3BlbiBhbnN3ZXIpfSIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBsb2NrZG93biIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBwb3N0LWxvY2tkb3duIiwKICAgICAgICAgICAgICAgICAgICJTY2hvb2wgeWVhcjogMTgvMTkiLAogICAgICAgICAgICAgICAgICAgIlF1ZXN0aW9uIHR5cGU6IG11bHRpcGxlIGNob2ljZSIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBsb2NrZG93biAkXFx0aW1lcyQgU2Nob29sIHllYXI6IDE4LzE5IiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IHBvc3QtbG9ja2Rvd24gJFxcdGltZXMkIFNjaG9vbCB5ZWFyOiAxOC8xOSIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBsb2NrZG93biAkXFx0aW1lcyQgUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IHBvc3QtbG9ja2Rvd24gJFxcdGltZXMkIFF1ZXN0aW9uIHR5cGU6IG11bHRpcGxlIGNob2ljZSIsCiAgICAgICAgICAgICAgICAgICAiU2Nob29sIHllYXI6IDE4LzE5ICRcXHRpbWVzJCBRdWVzdGlvbiB0eXBlOiBtdWx0aXBsZSBjaG9pY2UiLAogICAgICAgICAgICAgICAgICAgIlBlcmlvZDogbG9ja2Rvd24gJFxcdGltZXMkIFNjaG9vbCB5ZWFyOiAxOC8xOSAkXFx0aW1lcyQgUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IHBvc3QtbG9ja2Rvd24gJFxcdGltZXMkIFNjaG9vbCB5ZWFyOiAxOC8xOSAkXFx0aW1lcyQgUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIikKCiMgRm9ybWF0IHAtdmFsdWVzCm1fcnRfY29lZiRgUHIoPnx6fClgIDwtIGZvcm1hdC5wdmFsKG1fcnRfY29lZiRgUHIoPnx6fClgLCBlcHMgPSAuMDAxLCBkaWdpdHMgPSAzLCBmbGFnID0gIjAiKQptX3J0X2NvZWYkYFByKD58enwpYCA8LSBzdWIoJ14oPCk/MFsuXScsICdcXDEuJywgbV9ydF9jb2VmJGBQcig+fHp8KWApICMgUmVtb3ZlIGxlYWRpbmcgemVybwoKY2F0KGtuaXRyOjprYWJsZShtX3J0X2NvZWYsCiAgICAgICAgICAgICAgICAgYWxpZ24gPSBjKCJsIiwiciIsICJyIiwgInIiLCAiciIpLAogICAgICAgICAgICAgICAgIGRpZ2l0cyA9IGMoTkEsIDMsIDMsIDIsIE5BKSwKICAgICAgICAgICAgICAgICBjb2wubmFtZXMgPSBjKCJFZmZlY3QiLCAiJGIkIiwgIlNFIiwgIiR6JCIsICIkcCQiKSwKICAgICAgICAgICAgICAgICBmb3JtYXQgPSAibGF0ZXgiLAogICAgICAgICAgICAgICAgIGJvb2t0YWJzID0gVFJVRSwKICAgICAgICAgICAgICAgICBlc2NhcGUgPSBGQUxTRSksCiAgICBmaWxlID0gIi4uL291dHB1dC9tX3J0X3RhYmxlLnRleCIpCmBgYAoKVmlzdWFsaXNlIHRoZSBtb2RlbCBmaXQ6CmBgYHtyfQpydF9maXQgPC0gZXhwYW5kLmdyaWQocGVyaW9kID0gYygicHJlLWxvY2tkb3duIiwgImR1cmluZy1sb2NrZG93biIsICJwb3N0LWxvY2tkb3duIiksIHNjaG9vbF95ZWFyID0gYygiMTgvMTkiLCAiMTkvMjAiKSwgbWNxID0gYyhUUlVFLCBGQUxTRSkpCnJ0X2ZpdCA8LSBjYmluZChydF9maXQsIHJ0ID0gcHJlZGljdChtX3J0LCB0eXBlID0gInJlc3BvbnNlIiwgcmUuZm9ybSA9IE5BLCBuZXdkYXRhID0gcnRfZml0KSkKcnRfZml0CgpnZ3Bsb3QocnRfZml0LCBhZXMoeCA9IHBlcmlvZCwgeSA9IHJ0LCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgbHR5ID0gbWNxLCBncm91cCA9IGludGVyYWN0aW9uKG1jcSwgc2Nob29sX3llYXIpKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDE1MDAsIDQwMDApKSArCiAgdGhlbWVfcGFwZXIKYGBgCgpFbXBpcmljYWwgbWVhbnM6CmBgYHtyfQpydF9tZWFuIDwtIHJ0X21vZGVsX2RhdFsoY291cnNlID09ICJFbmdsaXNoIiAmIG1jcSA9PSBUUlVFKSB8IGNvdXJzZSA9PSAiRnJlbmNoIiwgLihydCA9IG1lYW4ocnRfbWVkaWFuKSksIGJ5ID0gLihwZXJpb2QsIHNjaG9vbF95ZWFyLCBtY3EsIHVzZXIsIGNvdXJzZSldWywgLihydCA9IG1lYW4ocnQpLCBydF9zZCA9IHNkKHJ0KSksIGJ5ID0gLihwZXJpb2QsIHNjaG9vbF95ZWFyLCBtY3EsIGNvdXJzZSldCnJ0X21lYW5bLCBzY2hvb2xfeWVhciA6PSBmYWN0b3Ioc2Nob29sX3llYXIsIGxldmVscyA9IGMoIjE4LzE5IiwgIjE5LzIwIikpXQpydF9tZWFuCmBgYAoKYGBge3J9CmdncGxvdChydF9tZWFuLCBhZXMoeCA9IHBlcmlvZCwgeSA9IHJ0LCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgbHR5ID0gbWNxLCBncm91cCA9IGludGVyYWN0aW9uKG1jcSwgc2Nob29sX3llYXIpKSkgKwogIGZhY2V0X2dyaWQofiBjb3Vyc2UpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygxNTAwLCAzMDAwKSkgKwogIHRoZW1lX3BhcGVyCmBgYAoKCiMjIENvbWJpbmF0aW9uIHBsb3QKCmBgYHtyfQpwX2xlZ2VuZCA8LSBnZXRfbGVnZW5kKHBfYWNjKQoKcF9hY2MgPC0gcF9hY2MgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSwgZmlsbCA9IEZBTFNFLCBsdHkgPSBGQUxTRSkKCnBfcnQgPC0gcF9ydCArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFLCBmaWxsID0gRkFMU0UsIGx0eSA9IEZBTFNFKQpgYGAKCkNvbWJpbmUgcGxvdHM6CmBgYHtyfQpwbG90X2dyaWQoCiAgcGxvdF9ncmlkKHBfYWNjLCBwX3J0LAogICAgICAgICAgICBuY29sID0gMSwKICAgICAgICAgICAgbGFiZWxzID0gYygiQSIsICJCIikpLAogIHBfbGVnZW5kLAogIHJlbF93aWR0aHMgPSBjKDEsIC4yKQopCgpnZ3NhdmUoIi4uL291dHB1dC9jb21iaV9hY2NfcnQucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzLjUpCmdnc2F2ZSgiLi4vb3V0cHV0L2NvbWJpX2FjY19ydC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDMuNSkKYGBgCgoKCiMgTGVhcm5pbmcgcHJvZ3Jlc3MKCkdldCB0aGUgdW5pcXVlIGJvb2sgY2hhcHRlciBJRHMgb24gZWFjaCBkYXk6CmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKCnByb2dyZXNzIDwtIGRiR2V0UXVlcnkoZGIsCiAgICAgICAgICAgICAgICAgICAgICAgIlNFTEVDVCBESVNUSU5DVCByLmJvb2tfaW5mb19pZCBBUyAnYm9va19pbmZvX2lkJywKICAgICAgICAgICAgICAgICAgICAgICAgci5tZXRob2QgQVMgJ21ldGhvZCcsCiAgICAgICAgICAgICAgICAgICAgICAgIERBVEUoci5kYXRlICsgMzYwMCwgJ3VuaXhlcG9jaCcpIEFTICdkb3knLAogICAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAndHJpYWxzJwogICAgICAgICAgICAgICAgICAgICAgICBGUk9NICdyZXNwb25zZXMnIHIKICAgICAgICAgICAgICAgICAgICAgICAgR1JPVVAgQlkgci5tZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgIHIuYm9va19pbmZvX2lkLAogICAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKTsiCiAgICAgICAgICAgICAgICAgICAgICAgKQoKZGJfZGlzY29ubmVjdChkYikKCnNldERUKHByb2dyZXNzKQpgYGAKCkpvaW4gd2l0aCB0aGUgYm9vayBjaGFwdGVyIGluZm9ybWF0aW9uOgpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCmJvb2tfaW5mbyA8LSBkYkdldFF1ZXJ5KGRiLCAiU0VMRUNUIERJU1RJTkNUICogRlJPTSAnYm9va19pbmZvJyIpCmRiX2Rpc2Nvbm5lY3QoZGIpCgpzZXREVChib29rX2luZm8pCmBgYAoKYGBge3J9CnByb2dyZXNzW2Jvb2tfaW5mb1tib29rX3R5cGUgPT0gIkhvb2ZkYm9layIsXSwgb24gID0gImJvb2tfaW5mb19pZCIsIGMoIm1ldGhvZF9ncm91cCIsICJib29rX3RpdGxlIiwgImNoYXB0ZXIiKSA6PSAuKGkubWV0aG9kX2dyb3VwLCBpLmJvb2tfdGl0bGUsIGkuY2hhcHRlcildCmBgYAoKQWRkIHNlbnNpYmxlIGNvdXJzZSBuYW1lczoKYGBge3J9CnByb2dyZXNzWywgY291cnNlIDo9IGlmZWxzZShtZXRob2QgPT0gIkdyYW5kZXMgTGlnbmVzIiwgIkZyZW5jaCIsIGlmZWxzZShtZXRob2QgPT0gIlN0ZXBwaW5nIFN0b25lcyIsICJFbmdsaXNoIiwgIkdlcm1hbiIpKV0KYGBgCgpBZGQgYSBzY2hvb2wgeWVhciBjb2x1bW4gKGN1dG9mZiBkYXRlOiAxIEF1Z3VzdCk6CmBgYHtyfQpwcm9ncmVzc1ssIGRveV9wb3NpeCA6PSBhcy5QT1NJWGN0KGRveSldCnByb2dyZXNzWywgc2Nob29sX3llYXIgOj0gaWZlbHNlKGRveV9wb3NpeCA8ICIyMDE5LTA4LTAxIiwgIjE4LzE5IiwgIjE5LzIwIildCmBgYAoKQ29uc29saWRhdGUgY291bnQgYnkgZGF5IGFuZCBjaGFwdGVyOgpgYGB7cn0KcHJvZ3Jlc3NfYnlfZGF5IDwtIHByb2dyZXNzWywgLih0cmlhbHMgPSBzdW0odHJpYWxzKSksIGJ5ID0gLihzY2hvb2xfeWVhciwgZG95X3Bvc2l4LCBjb3Vyc2UsIG1ldGhvZF9ncm91cCwgYm9va190aXRsZSwgY2hhcHRlcildCmBgYAoKClNpbXBsaWZ5IGxldmVsIG5hbWVzOgpgYGB7cn0KIyBLZWVwIGFsbCBkaXN0aW5jdGlvbnMKcHJvZ3Jlc3NfYnlfZGF5WywgYm9va190aXRsZV9zaW1wbGUgOj0gc3RyaW5ncjo6c3RyX3N1Yihib29rX3RpdGxlLCAzLCAtMTApXQpwcm9ncmVzc19ieV9kYXlbLCBib29rX3RpdGxlX3NpbXBsZSA6PSBmYWN0b3IoYm9va190aXRsZV9zaW1wbGUsIGxldmVscyA9IGMoInZtYm8gYi9sd29vIiwgInZtYm8gYiIsICJ2bWJvIGJrIiwgInZtYm8gayIsICJ2bWJvIGtndCIsICJ2bWJvLWd0IiwgInZtYm8gZ3QiLCAidm1iby1ndC9oYXZvIiwgInZtYm8gKHQpaHYiLCAiaGF2byIsICJoYXZvIHZ3byIsICJ2d28iKSldCgojIFNpbXBsaWZ5IHRvIHRocmVlIGxldmVscwpwcm9ncmVzc19ieV9kYXlbLCBsZXZlbCA6PSBkcGx5cjo6Y2FzZV93aGVuKAogIGdyZXBsKCAiaHYiLCBib29rX3RpdGxlKSB+ICJHZW5lcmFsIHNlY29uZGFyeSAoaGF2bykiLAogIGdyZXBsKCJ2bWJvIiwgYm9va190aXRsZSkgfiAiUHJlLXZvY2F0aW9uYWwgKHZtYm8pIiwKICBncmVwbCgiaGF2byIsIGJvb2tfdGl0bGUpIH4gIkdlbmVyYWwgc2Vjb25kYXJ5IChoYXZvKSIsCiAgZ3JlcGwoInZ3byIsIGJvb2tfdGl0bGUpIH4gIlByZS11bml2ZXJzaXR5ICh2d28pIiwKICBUUlVFIH4gIk90aGVyIildCnByb2dyZXNzX2J5X2RheVssIGxldmVsIDo9IGZhY3RvcihsZXZlbCwgbGV2ZWxzID0gYygiT3RoZXIiLCAiUHJlLXZvY2F0aW9uYWwgKHZtYm8pIiwgIkdlbmVyYWwgc2Vjb25kYXJ5IChoYXZvKSIsICJQcmUtdW5pdmVyc2l0eSAodndvKSIpKV0KYGBgCgpTaW1wbGlmeSB5ZWFyIG5hbWVzOgpgYGB7cn0KcHJvZ3Jlc3NfYnlfZGF5WywgeWVhciA6PSBkcGx5cjo6Y2FzZV93aGVuKAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMSAoNWUgRWQuKSIgfiAiWWVhciAxIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDIgKDVlIEVkLikiIH4gIlllYXIgMiIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAzICg1ZSBFZC4pIiB+ICJZZWFyIDMiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMy80ICg1ZSBFZC4pIiB+ICJZZWFyIDMvNCIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciA0ICg1ZSBFZC4pIiB+ICJZZWFyIDQiLAogIG1ldGhvZF9ncm91cCA9PSAiVHdlZWRlIEZhc2UgKDZlIEVkLikiIH4gIlR3ZWVkZSBGYXNlIiwKICBUUlVFIH4gIk90aGVyIildCmBgYAoKClNpbXBsaWZ5IGNoYXB0ZXIgbmFtZXM6CmBgYHtyfQojIEluIG1vc3QgY2FzZXMsIHRoZSBjaGFwdGVyIG5hbWUgc3RhcnRzIHdpdGggYSBudW1iZXIKcHJvZ3Jlc3NfYnlfZGF5WywgY2hhcHRlcl9zaW1wbGUgOj0gZmFjdG9yKGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX2V4dHJhY3QoY2hhcHRlciwgIl5cXGR7MSwyfSIpKSldCgojIFJlbWFpbmluZyBjYXNlczoKdW5pcXVlKHByb2dyZXNzX2J5X2RheVtpcy5uYShjaGFwdGVyX3NpbXBsZSksXSRjaGFwdGVyKQoKIyBDb21iaW5lIHRoZXNlIGNoYXB0ZXJzIGludG8gYW4gIm90aGVyIiBjYXRlZ29yeQpwcm9ncmVzc19ieV9kYXlbaXMubmEoY2hhcHRlcl9zaW1wbGUpLCBjaGFwdGVyX3NpbXBsZSA6PSAiTyJdCmBgYAoKCkFsaWduIHNjaG9vbCB5ZWFyczoKYGBge3J9CnByb2dyZXNzX2J5X2RheVtzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KcHJvZ3Jlc3NfYnlfZGF5W3NjaG9vbF95ZWFyID09ICIxOS8yMCIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGRveV9wb3NpeF0KYGBgCgpVc2UgY3V0LkRhdGUoKSB0byBiaW4gZGF0ZXMgYnkgd2VlayBhbmQgbW9udGguIEVhY2ggZGF5IGlzIGFzc2lnbmVkIHRoZSBkYXRlIG9mIHRoZSBtb3N0IHJlY2VudCBNb25kYXkuCmBgYHtyfQpwcm9ncmVzc19ieV9kYXlbLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJ3ZWVrIildCnByb2dyZXNzX2J5X2RheVssIGRveV9wb3NpeF9hbGlnbmVkX21vbnRoIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJtb250aCIpXQpgYGAKCkNhbGN1bGF0ZSBwcm9wb3J0aW9ucyBieSB3ZWVrIGFuZCBtb250aDoKYGBge3J9CnByb2dyZXNzX2J5X3dlZWsgPC0gcHJvZ3Jlc3NfYnlfZGF5WywgLih0cmlhbHMgPSBzdW0odHJpYWxzKSksIGJ5ID0gLihzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfd2VlaywgY291cnNlLCBsZXZlbCwgeWVhciwgY2hhcHRlcl9zaW1wbGUpXQpwcm9ncmVzc19ieV93ZWVrWywgcHJvcCA6PSB0cmlhbHMvc3VtKHRyaWFscyksIGJ5ID0gLihzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfd2VlaywgY291cnNlLCBsZXZlbCwgeWVhcildCgpwcm9ncmVzc19ieV9tb250aCA8LSBwcm9ncmVzc19ieV9kYXlbLCAuKHRyaWFscyA9IHN1bSh0cmlhbHMpKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF9tb250aCwgY291cnNlLCBsZXZlbCwgeWVhciwgY2hhcHRlcl9zaW1wbGUpXQpwcm9ncmVzc19ieV9tb250aFssIHByb3AgOj0gdHJpYWxzL3N1bSh0cmlhbHMpLCBieSA9IC4oc2Nob29sX3llYXIsIGRveV9wb3NpeF9hbGlnbmVkX21vbnRoLCBjb3Vyc2UsIGxldmVsLCB5ZWFyKV0KYGBgCgpgYGB7cn0Kc2V0b3JkZXIocHJvZ3Jlc3NfYnlfd2VlaywgY2hhcHRlcl9zaW1wbGUpCnNldG9yZGVyKHByb2dyZXNzX2J5X21vbnRoLCBjaGFwdGVyX3NpbXBsZSkKYGBgCgoKIyMjIEZyZW5jaApgYGB7cn0KcF9mcmVuY2hfeTEgPC0gZ2dwbG90KHByb2dyZXNzX2J5X3dlZWtbY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAxIl0sIGFlcyh4ID0gYXMuUE9TSVhjdChkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKSwgeSA9IHByb3AsIGZpbGwgPSBjaGFwdGVyX3NpbXBsZSwgZ3JvdXAgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKHNjaG9vbF95ZWFyIH4gbGV2ZWwpICsKICBnZW9tX2NvbChhbHBoYSA9IDAuNzUsIHdpZHRoID0gNyoyNCo2MCo2MCwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTAuMDEsIHltYXggPSAxLjAxLCBmaWxsID0gTkEsIGNvbG91ciA9ICJibGFjayIsIGx0eSA9IDIpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSwgbGltaXRzID0gYygwLDEpLCBicmVha3MgPSBjKDAsIC41ICwgMSkpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChuY29sID0gMikpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJTaGFyZSBvZiB0cmlhbHMiLAogICAgICAgZmlsbCA9ICJDaGFwdGVyIikgKwogIHRoZW1lX3BhcGVyCgpwX2ZyZW5jaF95MiA8LSBnZ3Bsb3QocHJvZ3Jlc3NfYnlfd2Vla1tjb3Vyc2UgPT0gIkZyZW5jaCIgJiB5ZWFyID09ICJZZWFyIDIiXSwgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gcHJvcCwgZmlsbCA9IGNoYXB0ZXJfc2ltcGxlLCBncm91cCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoc2Nob29sX3llYXIgfiBsZXZlbCkgKwogIGdlb21fY29sKGFscGhhID0gMC43NSwgd2lkdGggPSA3KjI0KjYwKjYwLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMC4wMSwgeW1heCA9IDEuMDEsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgbHR5ID0gMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IGMoMCwgLjUgLCAxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAyKSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfZnJlbmNoX3kzIDwtIGdncGxvdChwcm9ncmVzc19ieV93ZWVrW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMy80Il0sIGFlcyh4ID0gYXMuUE9TSVhjdChkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKSwgeSA9IHByb3AsIGZpbGwgPSBjaGFwdGVyX3NpbXBsZSwgZ3JvdXAgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKHNjaG9vbF95ZWFyIH4gbGV2ZWwpICsKICBnZW9tX2NvbChhbHBoYSA9IDAuNzUsIHdpZHRoID0gNyoyNCo2MCo2MCwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTAuMDEsIHltYXggPSAxLjAxLCBmaWxsID0gTkEsIGNvbG91ciA9ICJibGFjayIsIGx0eSA9IDIpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSwgbGltaXRzID0gYygwLDEpLCBicmVha3MgPSBjKDAsIC41ICwgMSkpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChuY29sID0gMikpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJTaGFyZSBvZiB0cmlhbHMiLAogICAgICAgZmlsbCA9ICJDaGFwdGVyIikgKwogIHRoZW1lX3BhcGVyCgpwX3Byb2dyZXNzX2ZyZW5jaCA8LSBwbG90X2dyaWQocF9mcmVuY2hfeTEsIHBfZnJlbmNoX3kyLCBwX2ZyZW5jaF95MywgCiAgICAgICAgICBuY29sID0gMSwKICAgICAgICAgIGFsaWduID0gImh2IiwgYXhpcyA9ICJ0YmxyIiwKICAgICAgICAgIGxhYmVscyA9IGMoIlllYXIgMSIsICJZZWFyIDIiLCAiWWVhciAzLzQiKSwKICAgICAgICAgIGhqdXN0ID0gLTAuMSwKICAgICAgICAgIHNjYWxlID0gLjk1KQoKcF9wcm9ncmVzc19mcmVuY2gKCmdnc2F2ZSgiLi4vb3V0cHV0L3Byb2dyZXNzX2ZyZW5jaC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDkpCmdnc2F2ZSgiLi4vb3V0cHV0L3Byb2dyZXNzX2ZyZW5jaC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDYpCmBgYAoKCkRpZCB0aGUgc2hhcmUgb2YgdHJpYWxzIGNoYW5nZSBiZXR3ZWVuIHNjaG9vbCB5ZWFycz8KV2UgY2FuIHNpbXBsaWZ5IHRoZSBhbmFseXNpcyBieSBhZ2dyZWdhdGluZyBvdmVyIHRoZSB3aG9sZSBsb2NrZG93biBwZXJpb2QuCmBgYHtyfQpwcm9ncmVzc19sb2NrZG93biA8LSBwcm9ncmVzc19ieV9kYXlbYmV0d2Vlbihkb3lfcG9zaXhfYWxpZ25lZCwgZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgZGF0ZV9zY2hvb2xzX29wZW5lZCksIC4odHJpYWxzID0gc3VtKHRyaWFscykpLCBieSA9IC4oc2Nob29sX3llYXIsIGNvdXJzZSwgbGV2ZWwsIHllYXIsIGNoYXB0ZXJfc2ltcGxlKV0KCiMgRmlsbCBpbiBtaXNzaW5nIHJvd3MgKG9jY3VycyB3aGVuIGNoYXB0ZXIgd2FzIG9ubHkgc3R1ZGllZCBpbiBvbmUgb2YgdGhlIHR3byB5ZWFycykKcHJvZ3Jlc3NfbG9ja2Rvd24gPC0gYXMuZGF0YS50YWJsZSh0aWR5cjo6Y29tcGxldGUocHJvZ3Jlc3NfbG9ja2Rvd24sIHRpZHlyOjpuZXN0aW5nKGNvdXJzZSwgbGV2ZWwsIHllYXIsIGNoYXB0ZXJfc2ltcGxlKSwgc2Nob29sX3llYXIsIGZpbGwgPSBsaXN0KHRyaWFscyA9IDApKSkgCiAgCnByb2dyZXNzX2xvY2tkb3duWywgcHJvcCA6PSB0cmlhbHMvc3VtKHRyaWFscyksIGJ5ID0gLihzY2hvb2xfeWVhciwgY291cnNlLCBsZXZlbCwgeWVhcildCgpzZXRvcmRlcihwcm9ncmVzc19sb2NrZG93biwgY2hhcHRlcl9zaW1wbGUpCmBgYAoKYGBge3J9CmdncGxvdChwcm9ncmVzc19sb2NrZG93bltjb3Vyc2UgPT0gIkZyZW5jaCJdLCBhZXMoeCA9IHNjaG9vbF95ZWFyLCB5ID0gcHJvcCwgZmlsbCA9IGNoYXB0ZXJfc2ltcGxlLCBncm91cCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiB5ZWFyKSArCiAgZ2VvbV9jb2woY29sb3VyID0gTkEpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSkpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJTaGFyZSBvZiB0cmlhbHMiLAogICAgICAgZmlsbCA9ICJDaGFwdGVyIiwKICAgICAgIHRpdGxlID0gIkZyZW5jaCIpICsKICB0aGVtZV9wYXBlcgpgYGAKClBlcmZvcm0gYSBjaGktc3F1YXJlIHRlc3Qgb2YgaG9tb2dlbmVpdHkgdG8gZGV0ZXJtaW5lIHdoZXRoZXIgc2Nob29sIHllYXJzIGFyZSBzaWduaWZpY2FudGx5IGRpZmZlcmVudC4KCmBgYHtyfQpmb3IgKHkgaW4gc29ydCh1bmlxdWUocHJvZ3Jlc3NfbG9ja2Rvd24keWVhcikpKSB7CiAgZm9yIChsIGluIGxldmVscyhwcm9ncmVzc19sb2NrZG93biRsZXZlbCkpIHsKICAgIGQgPC0gcHJvZ3Jlc3NfbG9ja2Rvd25bY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSB5ICYgbGV2ZWwgPT0gbF0KICAgIGlmIChucm93KGQpID4gMCkgewogICAgICBwcmludChwYXN0ZSgiRnJlbmNoIiwgeSwgbCwgY29sbGFwc2U9ICIgIikpCiAgICAgIHByaW50KAogICAgICAgIGNoaXNxLnRlc3QoCiAgICAgICAgICBkY2FzdChkLCBzY2hvb2xfeWVhciB+IGNoYXB0ZXJfc2ltcGxlLCB2YWx1ZS52YXIgPSAidHJpYWxzIiwgZmlsbCA9IDApWywgc2Nob29sX3llYXIgOj0gTlVMTF0KICAgICAgICApCiAgICAgICkKICAgIH0KICB9IAp9CmBgYAoKQ29uY2x1c2lvbjogYWxsIHRlc3RzIGluZGljYXRlIGEgZGlmZmVyZW5jZSBpbiBwcm9wb3J0aW9ucyBiZXR3ZWVuIHNjaG9vbCB5ZWFycyAocCA8PCAwLjAwMSkuCgpWaXN1YWxpc2UgdGhlIGNoYW5nZSBiZXR3ZWVuIHNjaG9vbCB5ZWFyczoKYGBge3J9CnByb2dyZXNzX2xvY2tkb3duWywgcHJvcF9jaGFuZ2UgOj0gcHJvcFtzY2hvb2xfeWVhciA9PSAiMTkvMjAiXSAtIHByb3Bbc2Nob29sX3llYXIgPT0gIjE4LzE5Il0sIGJ5ID0gLihjb3Vyc2UsIGxldmVsLCB5ZWFyLCBjaGFwdGVyX3NpbXBsZSldCgpnZ3Bsb3QocHJvZ3Jlc3NfbG9ja2Rvd25bc2Nob29sX3llYXIgPT0gIjE5LzIwIiAmIGNvdXJzZSA9PSAiRnJlbmNoIl0sIGFlcyh4ID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApLCBjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSwgZ3JvdXAgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKGxldmVsIH4geWVhciwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsdHkgPSAyKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ZW5kID0gY2hhcHRlcl9zaW1wbGUpLCB5ZW5kID0gMCkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoLTI1LCAyNSkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9ICJDaGFwdGVyIiwKICAgICAgIHkgPSAiQ2hhbmdlIGluIHRyaWFsIHNoYXJlXG4ocGVyY2VudGFnZSBwb2ludHMpIiwKICAgICAgIHRpdGxlID0gIkZyZW5jaCIpICsKICB0aGVtZV9wYXBlcgpgYGAKCkFyZSB0aGVzZSBjaGFuZ2VzIHJlYWxseSBpbXBvcnRhbnQ/IFdlIG1heSBleHBlY3QgYSBjZXJ0YWluIGFtb3VudCBvZiBmbHVjdHVhdGlvbiBiZXR3ZWVuIGFueSBwYWlyIG9mIHNjaG9vbCB5ZWFycy4gV2UgZG9uJ3QgaGF2ZSBkYXRhIGZyb20gYmVmb3JlIHRoZSAxOC8xOSBzY2hvb2wgeWVhciwgYnV0IHdlIGNhbiBsb29rIGF0IGhvdyB0aGUgbWFnbml0dWRlIG9mIGNoYW5nZXMgZHVyaW5nIHRoZSBsb2NrZG93biBwZXJpb2QgY29tcGFyZXMgdG8gY2hhbmdlcyBlYXJsaWVyIGluIHRoZSBzY2hvb2wgeWVhci4KClRvIGtlZXAgdGhpbmdzIGFzIGNvbXBhcmFibGUgYXMgcG9zc2libGUsIHVzZSBhIHNsaWRpbmcgdGltZSB3aW5kb3cgd2l0aCB0aGUgc2FtZSBzaXplIGFzIHRoZSBsb2NrZG93biBwZXJpb2Q6CmBgYHtyfQp0aW1lX3dpbmRvdyA8LSBhcy5udW1lcmljKHJvdW5kKGRhdGVfc2Nob29sc19vcGVuZWQgLSBkYXRlX3NjaG9vbHNfY2xvc2VkKSkKdGltZV93aW5kb3cKYGBgCgpgYGB7cn0KZGF0ZV9yYW5nZSA8LSBzb3J0KHVuaXF1ZShwcm9ncmVzc19ieV9kYXkkZG95X3Bvc2l4X2FsaWduZWQpKQpkYXRlX3JhbmdlIDwtIGRhdGVfcmFuZ2VbZGF0ZV9yYW5nZSA8IGRhdGVfc2Nob29sc19jbG9zZWRdCgpwcm9wX2NoYW5nZV93aW5kb3cgPC0gZGF0YS50YWJsZSgpCgpmb3IgKGkgaW4gMToobGVuZ3RoKGRhdGVfcmFuZ2UpIC0gYXMubnVtZXJpYyh0aW1lX3dpbmRvdykpKSB7CiAgZCA8LSBkYXRlX3JhbmdlW2k6KGkgKyB0aW1lX3dpbmRvdyAtIDEpXQogIHByb2dyZXNzX3dpbmRvdyA8LSBwcm9ncmVzc19ieV9kYXlbY291cnNlICVpbiUgYygiRnJlbmNoIiwgIkVuZ2xpc2giKSAmIGRveV9wb3NpeF9hbGlnbmVkICVpbiUgZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4odHJpYWxzID0gc3VtKHRyaWFscykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSAuKHNjaG9vbF95ZWFyLCBjb3Vyc2UsIGxldmVsLCB5ZWFyLCBjaGFwdGVyX3NpbXBsZSldCiAgCiAgIyBGaWxsIGluIG1pc3Npbmcgcm93cyAob2NjdXJzIHdoZW4gY2hhcHRlciB3YXMgb25seSBzdHVkaWVkIGluIG9uZSBvZiB0aGUgdHdvIHllYXJzKQogIHByb2dyZXNzX3dpbmRvdyA8LSBhcy5kYXRhLnRhYmxlKHRpZHlyOjpjb21wbGV0ZShwcm9ncmVzc193aW5kb3csIHRpZHlyOjpuZXN0aW5nKGNvdXJzZSwgbGV2ZWwsIHllYXIsIGNoYXB0ZXJfc2ltcGxlKSwgc2Nob29sX3llYXIsIGZpbGwgPSBsaXN0KHRyaWFscyA9IDApKSkgCiAgCiAgcHJvZ3Jlc3Nfd2luZG93WywgcHJvcCA6PSB0cmlhbHMvc3VtKHRyaWFscyksIGJ5ID0gLihzY2hvb2xfeWVhciwgY291cnNlLCBsZXZlbCwgeWVhcildCgogIHByb2dyZXNzX3dpbmRvd1ssIHByb3BfY2hhbmdlIDo9IHByb3Bbc2Nob29sX3llYXIgPT0gIjE5LzIwIl0gLSBwcm9wW3NjaG9vbF95ZWFyID09ICIxOC8xOSJdLCBieSA9IC4oY291cnNlLCBsZXZlbCwgeWVhciwgY2hhcHRlcl9zaW1wbGUpXQogIAogIHByb3BfY2hhbmdlX3dpbmRvdyA8LSByYmluZChwcm9wX2NoYW5nZV93aW5kb3csIHByb2dyZXNzX3dpbmRvd1tzY2hvb2xfeWVhciA9PSAiMTkvMjAiLF1bLHdpbmRvdyA6PSBpXVssZGF0ZV9taW4gOj0gbWluKGQpXVssZGF0ZV9tYXggOj0gbWF4KGQpXSkKfQpgYGAKCgpUaGUgZGVuc2l0eSBvZiB5ZWFyLXRvLXllYXIgY2hhbmdlcyBjYW4gYmUgdmlzdWFsaXNlZCBieSB0aW1lIHdpbmRvdzoKYGBge3J9CmdncGxvdChwcm9wX2NoYW5nZV93aW5kb3csIGFlcyh4ID0gcHJvcF9jaGFuZ2UgKiAxMDAsIHkgPSB3aW5kb3csIGdyb3VwID0gd2luZG93KSkgKwogIGdncmlkZ2VzOjpnZW9tX2RlbnNpdHlfcmlkZ2VzKGFscGhhID0gMC4xLCBzY2FsZSA9IDI1LCBmaWxsID0gTkEpICsKICBsYWJzKHggPSAiQ2hhbmdlIGluIHRyaWFsIHNoYXJlXG4ocGVyY2VudGFnZSBwb2ludHMpIiwKICAgICAgICAgeSA9ICJUaW1lIHdpbmRvdyIpICsKICB0aGVtZV9wYXBlcgpgYGAKCkNvbXBhcmUgdGhlIGFnZ3JlZ2F0ZWQgZGVuc2l0eSB0byB0aGUgY2hhbmdlcyBkdXJpbmcgdGhlIGxvY2tkb3duIHBlcmlvZDoKYGBge3J9CnByb3BfY2hhbmdlX2NvbWJpbmVkIDwtIHJiaW5kKHByb3BfY2hhbmdlX3dpbmRvd1ssIHBlcmlvZCA6PSAiUHJlLWxvY2tkb3duIl0sIHByb2dyZXNzX2xvY2tkb3duW2NvdXJzZSAlaW4lIGMoIkZyZW5jaCIsICJFbmdsaXNoIikgJiBzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBwZXJpb2QgOj0gIkxvY2tkb3duIl0sIGZpbGwgPSBUUlVFKQpwcm9wX2NoYW5nZV9jb21iaW5lZFssIHBlcmlvZCA6PSBmYWN0b3IocGVyaW9kLCBsZXZlbHMgPSBjKCJQcmUtbG9ja2Rvd24iLCAiTG9ja2Rvd24iKSldCgpnZ3Bsb3QocHJvcF9jaGFuZ2VfY29tYmluZWQsIGFlcyh4ID0gcHJvcF9jaGFuZ2UsIGNvbG91ciA9IHBlcmlvZCkpICsKICBnZW9tX2RlbnNpdHkoKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gIkNoYW5nZSBpbiB0cmlhbCBzaGFyZVxuKHBlcmNlbnRhZ2UgcG9pbnRzKSIsCiAgICAgICB5ID0gIkRlbnNpdHkiLAogICAgICAgY29sb3VyID0gTlVMTCkgKwogIHRoZW1lX3BhcGVyCmBgYAoKYGBge3J9CnByb3BfY2hhbmdlX3NkIDwtIHByb3BfY2hhbmdlX3dpbmRvd1ssIC4oc2QgPSBzZChwcm9wX2NoYW5nZSkgKiAxMDApLCBieSA9IC4oY291cnNlLCB5ZWFyLCBsZXZlbCldCmBgYAoKCkFkZCBib3VuZGFyaWVzIGJhc2VkIG9uIHRoZSB0eXBpY2FsIHNwcmVhZCB0byB0aGUgY2hhbmdlIHBsb3Q6CmBgYHtyfQpwX2NoYW5nZV9mcmVuY2ggPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duW3NjaG9vbF95ZWFyID09ICIxOS8yMCIgJiBjb3Vyc2UgPT0gIkZyZW5jaCJdLCBhZXMoY29sb3VyID0gY2hhcHRlcl9zaW1wbGUpKSArCiAgZmFjZXRfZ3JpZCh5ZWFyIH4gbGV2ZWwsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkZyZW5jaCJdLCBhZXMoeW1pbiA9IC0yKnNkLCB5bWF4ID0gMipzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTAiLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJGcmVuY2giXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDApICsKICBnZW9tX3BvaW50KGFlcyh4ID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKC0yMCwgMCwgMjApKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBndWlkZXMoY29sb3VyID0gRkFMU0UpICsKICBsYWJzKHggPSAiQ2hhcHRlciIsCiAgICAgICB5ID0gIkNoYW5nZSBpbiB0cmlhbCBzaGFyZVxuKHBlcmNlbnRhZ2UgcG9pbnRzKSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpKQoKcF9jaGFuZ2VfZnJlbmNoCgpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jaGFuZ2VfZnJlbmNoLnBkZiIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gNCkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY2hhbmdlX2ZyZW5jaC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmBgYAoKTWFrZSBhIGNvbWJpbmF0aW9uIHBsb3QgZm9yIGluIHRoZSBwYXBlcjoKYGBge3J9CnBsb3RfZ3JpZChwX2ZyZW5jaF95MSwgcF9mcmVuY2hfeTIsIHBfZnJlbmNoX3kzLCBwX2NoYW5nZV9mcmVuY2gsCiAgICAgICAgICBuY29sID0gMSwKICAgICAgICAgIGFsaWduID0gImh2IiwgYXhpcyA9ICJ0YmxyIiwKICAgICAgICAgIGxhYmVscyA9IGMoIlllYXIgMSIsICJZZWFyIDIiLCAiWWVhciAzLzQiLCAiQ2hhbmdlIiksCiAgICAgICAgICByZWxfaGVpZ2h0cyA9IGMoMSwgMSwgMSwgMS41KSwKICAgICAgICAgIGhqdXN0ID0gLTAuMSwKICAgICAgICAgIHNjYWxlID0gLjk1KQoKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY29tYmlfZnJlbmNoLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY29tYmlfZnJlbmNoLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKYGBgCgoKYGBge3J9CnBfY2hhbmdlX2ZyZW5jaF95MSA8LSBnZ3Bsb3QocHJvZ3Jlc3NfbG9ja2Rvd25bc2Nob29sX3llYXIgPT0gIjE5LzIwIiAmIGNvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMSJdLCBhZXMoY29sb3VyID0gY2hhcHRlcl9zaW1wbGUpKSArCiAgZmFjZXRfZ3JpZCguIH4gbGV2ZWwsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkZyZW5jaCIgJiB5ZWFyID09ICJZZWFyIDEiXSwgYWVzKHltaW4gPSAtMipzZCwgeW1heCA9IDIqc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMDAsIGZpbGwgPSAiZ3JleTkwIiwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMSJdLCBhZXMoeW1pbiA9IC1zZCwgeW1heCA9IHNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAsIGZpbGwgPSAiZ3JleTc1IiwgY29sb3VyID0gTkEpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsdHkgPSAyKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gY2hhcHRlcl9zaW1wbGUsIHhlbmQgPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCkpLCB5ZW5kID0gMCwgYWxwaGEgPSAuNzUpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgYWxwaGEgPSAuNzUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gYygtMjAsIDAsIDIwKSwgbGltaXRzID0gYygtMzAsIDMwKSwgbGFiZWxzID0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KHN1ZmZpeCA9ICIgcHAiKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQ2hhbmdlIikgKwogIHRoZW1lX3BhcGVyICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpKQoKcF9jaGFuZ2VfZnJlbmNoX3kyIDwtIGdncGxvdChwcm9ncmVzc19sb2NrZG93bltzY2hvb2xfeWVhciA9PSAiMTkvMjAiICYgY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAyIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMiJdLCBhZXMoeW1pbiA9IC0yKnNkLCB5bWF4ID0gMipzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTAiLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAyIl0sIGFlcyh5bWluID0gLXNkLCB5bWF4ID0gc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMCwgZmlsbCA9ICJncmV5NzUiLCBjb2xvdXIgPSBOQSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeGVuZCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIHllbmQgPSAwLCBhbHBoYSA9IC43NSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCkpLCBhbHBoYSA9IC43NSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKC0yMCwgMCwgMjApLCAgbGltaXRzID0gYygtMzAsIDMwKSwgbGFiZWxzID0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KHN1ZmZpeCA9ICIgcHAiKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQ2hhbmdlIikgKwogIHRoZW1lX3BhcGVyICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpKQoKcF9jaGFuZ2VfZnJlbmNoX3kzIDwtIGdncGxvdChwcm9ncmVzc19sb2NrZG93bltzY2hvb2xfeWVhciA9PSAiMTkvMjAiICYgY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAzLzQiXSwgYWVzKGNvbG91ciA9IGNoYXB0ZXJfc2ltcGxlKSkgKwogIGZhY2V0X2dyaWQoLiB+IGxldmVsLCBzY2FsZXMgPSAiZnJlZV94IikgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAzLzQiXSwgYWVzKHltaW4gPSAtMipzZCwgeW1heCA9IDIqc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMDAsIGZpbGwgPSAiZ3JleTkwIiwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMy80Il0sIGFlcyh5bWluID0gLXNkLCB5bWF4ID0gc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMCwgZmlsbCA9ICJncmV5NzUiLCBjb2xvdXIgPSBOQSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeGVuZCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIHllbmQgPSAwLCBhbHBoYSA9IC43NSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCkpLCBhbHBoYSA9IC43NSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKC0yMCwgMCwgMjApLCAgbGltaXRzID0gYygtMzAsIDMwKSwgbGFiZWxzID0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KHN1ZmZpeCA9ICIgcHAiKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQ2hhbmdlIikgKwogIHRoZW1lX3BhcGVyICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpKQoKZmlsbGVyX3Bsb3QgPC0gcXBsb3QoKSArIAogIHRoZW1lX25vdGhpbmcoKSArIAogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCnBsb3RfZ3JpZCgKICAgICAgICAgIGZpbGxlcl9wbG90LAogICAgICAgICAgcF9mcmVuY2hfeTEsIGZpbGxlcl9wbG90LCBwX2NoYW5nZV9mcmVuY2hfeTEsIGZpbGxlcl9wbG90LAogICAgICAgICAgcF9mcmVuY2hfeTIsIGZpbGxlcl9wbG90LCBwX2NoYW5nZV9mcmVuY2hfeTIsIGZpbGxlcl9wbG90LCAKICAgICAgICAgIHBfZnJlbmNoX3kzLCBmaWxsZXJfcGxvdCwgcF9jaGFuZ2VfZnJlbmNoX3kzLAogICAgICAgICAgbmNvbCA9IDEsCiAgICAgICAgICBhbGlnbiA9ICJodiIsIGF4aXMgPSAidGJsciIsCiAgICAgICAgICBsYWJlbHMgPSBjKE5BLAogICAgICAgICAgICAgICAgICAgICAiWWVhciAxIiwgTkEsIE5BLCBOQSwKICAgICAgICAgICAgICAgICAgICAgIlllYXIgMiIsIE5BLCBOQSwgTkEsCiAgICAgICAgICAgICAgICAgICAgICJZZWFyIDMvNCIsIE5BLCBOQSksCiAgICAgICAgICByZWxfaGVpZ2h0cyA9IGMoLjEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIDEsIC0uMiwgLjc1LCAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAxLCAtLjIsIC43NSwgLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgMSwgLS4yLCAuNzUpLAogICAgICAgICAgaGp1c3QgPSAtMC4xLAogICAgICAgICAgdmp1c3QgPSAtMC4xLAogICAgICAgICAgc2NhbGUgPSAuOTUpCgpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jb21iaV9hbHRfZnJlbmNoLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY29tYmlfYWx0X2ZyZW5jaC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDkpCmBgYAoKCiMjIyBFbmdsaXNoCgpOT1RFOiBjaGFwdGVycyB3aXRob3V0IGEgbnVtYmVyIChCcmlkZ2luZyB0aGUgR2FwLCBFeGFtIFByZXBhcmF0aW9uKSBhcmUgc2hvd24gYXMgIk8iIGluIHRoZSBwbG90LgpUaGV5IGRvbid0IHNlZW0gdG8gZml0IG5lYXRseSBpbiB0aGUgY2hhcHRlciBzZXF1ZW5jZSwgc28gSSdtIGdyb3VwaW5nIHRoZW0gdG9nZXRoZXIuCmBgYHtyfQpwX2VuZ2xpc2hfeTEgPC0gZ2dwbG90KHByb2dyZXNzX2J5X3dlZWtbbGV2ZWwgIT0gIk90aGVyIl1bLCBsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXVtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgeWVhciA9PSAiWWVhciAxIl0sIGFlcyh4ID0gYXMuUE9TSVhjdChkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKSwgeSA9IHByb3AsIGZpbGwgPSBjaGFwdGVyX3NpbXBsZSwgZ3JvdXAgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKHNjaG9vbF95ZWFyIH4gbGV2ZWwsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fY29sKGFscGhhID0gMC43NSwgd2lkdGggPSA3KjI0KjYwKjYwLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMC4wMSwgeW1heCA9IDEuMDEsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgbHR5ID0gMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IGMoMCwgLjUgLCAxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAyKSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfZW5nbGlzaF95MiA8LSBnZ3Bsb3QocHJvZ3Jlc3NfYnlfd2Vla1tsZXZlbCAhPSAiT3RoZXIiXVssIGxldmVsIDo9IGZhY3RvcihsZXZlbCldW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiB5ZWFyID09ICJZZWFyIDIiXSwgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gcHJvcCwgZmlsbCA9IGNoYXB0ZXJfc2ltcGxlLCBncm91cCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoc2Nob29sX3llYXIgfiBsZXZlbCwgZHJvcCA9IEZBTFNFKSArCiAgZ2VvbV9jb2woYWxwaGEgPSAwLjc1LCB3aWR0aCA9IDcqMjQqNjAqNjAsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0wLjAxLCB5bWF4ID0gMS4wMSwgZmlsbCA9IE5BLCBjb2xvdXIgPSAiYmxhY2siLCBsdHkgPSAyKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLCAKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCksIGxpbWl0cyA9IGMoMCwxKSwgYnJlYWtzID0gYygwLCAuNSAsIDEpKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobmNvbCA9IDIpKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiU2hhcmUgb2YgdHJpYWxzIiwKICAgICAgIGZpbGwgPSAiQ2hhcHRlciIpICsKICB0aGVtZV9wYXBlcgoKcF9lbmdsaXNoX3kzIDwtIGdncGxvdChwcm9ncmVzc19ieV93ZWVrW2xldmVsICE9ICJPdGhlciJdWywgbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV1bY291cnNlID09ICJFbmdsaXNoIiAmIHllYXIgPT0gIlllYXIgMyJdLCBhZXMoeCA9IGFzLlBPU0lYY3QoZG95X3Bvc2l4X2FsaWduZWRfd2VlayksIHkgPSBwcm9wLCBmaWxsID0gY2hhcHRlcl9zaW1wbGUsIGdyb3VwID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChzY2hvb2xfeWVhciB+IGxldmVsLCBkcm9wID0gRkFMU0UpICsKICBnZW9tX2NvbChhbHBoYSA9IDAuNzUsIHdpZHRoID0gNyoyNCo2MCo2MCwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTAuMDEsIHltYXggPSAxLjAxLCBmaWxsID0gTkEsIGNvbG91ciA9ICJibGFjayIsIGx0eSA9IDIpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSwgbGltaXRzID0gYygwLDEpLCBicmVha3MgPSBjKDAsIC41ICwgMSkpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChuY29sID0gMikpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJTaGFyZSBvZiB0cmlhbHMiLAogICAgICAgZmlsbCA9ICJDaGFwdGVyIikgKwogIHRoZW1lX3BhcGVyCgpwX2VuZ2xpc2hfeTQgPC0gZ2dwbG90KHByb2dyZXNzX2J5X3dlZWtbbGV2ZWwgIT0gIk90aGVyIl1bLCBsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXVtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgeWVhciA9PSAiWWVhciA0Il0sIGFlcyh4ID0gYXMuUE9TSVhjdChkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKSwgeSA9IHByb3AsIGZpbGwgPSBjaGFwdGVyX3NpbXBsZSwgZ3JvdXAgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKHNjaG9vbF95ZWFyIH4gbGV2ZWwsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fY29sKGFscGhhID0gMC43NSwgd2lkdGggPSA3KjI0KjYwKjYwLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMC4wMSwgeW1heCA9IDEuMDEsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgbHR5ID0gMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IGMoMCwgLjUgLCAxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAyKSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfcHJvZ3Jlc3NfZW5nbGlzaCA8LSBwbG90X2dyaWQocF9lbmdsaXNoX3kxLCBwX2VuZ2xpc2hfeTIsIHBfZW5nbGlzaF95MywgcF9lbmdsaXNoX3k0LAogICAgICAgICAgbmNvbCA9IDEsCiAgICAgICAgICBhbGlnbiA9ICJodiIsIGF4aXMgPSAidGJsciIsCiAgICAgICAgICBsYWJlbHMgPSBjKCJZZWFyIDEiLCAiWWVhciAyIiwgIlllYXIgMyIsICJZZWFyIDQiKSwKICAgICAgICAgIGhqdXN0ID0gLTAuMSwKICAgICAgICAgIHNjYWxlID0gLjk1KQoKcF9wcm9ncmVzc19lbmdsaXNoCgpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19lbmdsaXNoLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfZW5nbGlzaC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDkpCmBgYAoKRGlkIHRoZSBzaGFyZSBvZiB0cmlhbHMgY2hhbmdlIGJldHdlZW4gc2Nob29sIHllYXJzPwoKYGBge3J9CmdncGxvdChwcm9ncmVzc19sb2NrZG93bltjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIl0sIGFlcyh4ID0gc2Nob29sX3llYXIsIHkgPSBwcm9wLCBmaWxsID0gY2hhcHRlcl9zaW1wbGUsIGdyb3VwID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IHllYXIpICsKICBnZW9tX2NvbChjb2xvdXIgPSBOQSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCksIGxpbWl0cyA9IGMoMCwxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiLAogICAgICAgdGl0bGUgPSAiRW5nbGlzaCIpICsKICB0aGVtZV9wYXBlcgpgYGAKCkNoYW5nZSBiZXR3ZWVuIHNjaG9vbCB5ZWFyczoKYGBge3J9CnBfY2hhbmdlX2VuZ2xpc2ggPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duW3NjaG9vbF95ZWFyID09ICIxOS8yMCIgJiBjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKHllYXIgfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiBsZXZlbCAhPSAiT3RoZXIiXSwgYWVzKHltaW4gPSAtMipzZCwgeW1heCA9IDIqc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMDAsIGZpbGwgPSAiZ3JleTkwIiwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiBsZXZlbCAhPSAiT3RoZXIiXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDApICsKICBnZW9tX3BvaW50KGFlcyh4ID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFKSArCiAgbGFicyh4ID0gIkNoYXB0ZXIiLAogICAgICAgeSA9ICJDaGFuZ2UgaW4gdHJpYWwgc2hhcmVcbihwZXJjZW50YWdlIHBvaW50cykiKSArCiAgdGhlbWVfcGFwZXIgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSkKCnBfY2hhbmdlX2VuZ2xpc2ggCgpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jaGFuZ2VfZW5nbGlzaC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDYpCmdnc2F2ZSgiLi4vb3V0cHV0L3Byb2dyZXNzX2NoYW5nZV9lbmdsaXNoLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNikKYGBgCgpQZXJmb3JtIGEgY2hpLXNxdWFyZSB0ZXN0IG9mIGhvbW9nZW5laXR5IHRvIGRldGVybWluZSB3aGV0aGVyIHNjaG9vbCB5ZWFycyBhcmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQuCgpgYGB7cn0KZm9yICh5IGluIHNvcnQodW5pcXVlKHByb2dyZXNzX2xvY2tkb3duJHllYXIpKSkgewogIGZvciAobCBpbiBsZXZlbHMocHJvZ3Jlc3NfbG9ja2Rvd24kbGV2ZWwpKSB7CiAgICBkIDwtIHByb2dyZXNzX2xvY2tkb3duW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiB5ZWFyID09IHkgJiBsZXZlbCA9PSBsXQogICAgaWYgKG5yb3coZCkgPiAwKSB7CiAgICAgIHByaW50KHBhc3RlKCJFbmdsaXNoIiwgeSwgbCwgY29sbGFwc2U9ICIgIikpCiAgICAgIHByaW50KAogICAgICAgIGNoaXNxLnRlc3QoCiAgICAgICAgICBkY2FzdChkLCBzY2hvb2xfeWVhciB+IGNoYXB0ZXJfc2ltcGxlLCB2YWx1ZS52YXIgPSAidHJpYWxzIiwgZmlsbCA9IDApWywgc2Nob29sX3llYXIgOj0gTlVMTF0KICAgICAgICApCiAgICAgICkKICAgIH0KICB9IAp9CmBgYAoKQ29uY2x1c2lvbjogYWxsIHRlc3RzIGluZGljYXRlIGEgZGlmZmVyZW5jZSBpbiBwcm9wb3J0aW9ucyBiZXR3ZWVuIHNjaG9vbCB5ZWFycyAocCA8PCAwLjAwMSkuCgoKTWFrZSBhIGNvbWJpbmF0aW9uIHBsb3QgZm9yIGluIHRoZSBwYXBlcjoKYGBge3J9CnByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2ggPC0gcHJvZ3Jlc3NfbG9ja2Rvd25bbGV2ZWwgIT0gIk90aGVyIiAmIHNjaG9vbF95ZWFyID09ICIxOS8yMCIgJiBjb3Vyc2UgPT0gIkVuZ2xpc2giXQpwcm9ncmVzc19sb2NrZG93bl9lbmdsaXNoWywgbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0KCnBfY2hhbmdlX2VuZ2xpc2hfeTEgPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2hbeWVhciA9PSAiWWVhciAxIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiB5ZWFyID09ICJZZWFyIDEiXVssbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHllYXIgPT0gIlllYXIgMSJdWyxsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDAsIGFscGhhID0gLjc1KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIGFscGhhID0gLjc1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTEwLCAwLCAxMCksIGxpbWl0cyA9IGMoLTIwLCAyMCksIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyX2Zvcm1hdChzdWZmaXggPSAiIHBwIikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkNoYW5nZSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCnBfY2hhbmdlX2VuZ2xpc2hfeTIgPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2hbeWVhciA9PSAiWWVhciAyIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiB5ZWFyID09ICJZZWFyIDIiXVssbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHllYXIgPT0gIlllYXIgMiJdWyxsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDAsIGFscGhhID0gLjc1KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIGFscGhhID0gLjc1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTEwLCAwLCAxMCksIGxpbWl0cyA9IGMoLTIwLCAyMCksIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyX2Zvcm1hdChzdWZmaXggPSAiIHBwIikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkNoYW5nZSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCnBfY2hhbmdlX2VuZ2xpc2hfeTMgPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2hbeWVhciA9PSAiWWVhciAzIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiB5ZWFyID09ICJZZWFyIDMiXVssbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHllYXIgPT0gIlllYXIgMyJdWyxsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDAsIGFscGhhID0gLjc1KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIGFscGhhID0gLjc1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTEwLCAwLCAxMCksIGxpbWl0cyA9IGMoLTIwLCAyMCksIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyX2Zvcm1hdChzdWZmaXggPSAiIHBwIikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkNoYW5nZSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCnBfY2hhbmdlX2VuZ2xpc2hfeTQgPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2hbeWVhciA9PSAiWWVhciA0Il0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiB5ZWFyID09ICJZZWFyIDQiXVssbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHllYXIgPT0gIlllYXIgNCJdWyxsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDAsIGFscGhhID0gLjc1KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIGFscGhhID0gLjc1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTEwLCAwLCAxMCksIGxpbWl0cyA9IGMoLTIwLCAyMCksIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyX2Zvcm1hdChzdWZmaXggPSAiIHBwIikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkNoYW5nZSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCmZpbGxlcl9wbG90IDwtIHFwbG90KCkgKyAKICB0aGVtZV9ub3RoaW5nKCkgKyAKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSkpCgpwbG90X2dyaWQoCiAgICAgICAgICBmaWxsZXJfcGxvdCwKICAgICAgICAgIHBfZW5nbGlzaF95MSwgZmlsbGVyX3Bsb3QsIHBfY2hhbmdlX2VuZ2xpc2hfeTEsIGZpbGxlcl9wbG90LAogICAgICAgICAgcF9lbmdsaXNoX3kyLCBmaWxsZXJfcGxvdCwgcF9jaGFuZ2VfZW5nbGlzaF95MiwgZmlsbGVyX3Bsb3QsIAogICAgICAgICAgcF9lbmdsaXNoX3kzLCBmaWxsZXJfcGxvdCwgcF9jaGFuZ2VfZW5nbGlzaF95MywgZmlsbGVyX3Bsb3QsCiAgICAgICAgICBwX2VuZ2xpc2hfeTQsIGZpbGxlcl9wbG90LCBwX2NoYW5nZV9lbmdsaXNoX3k0LAogICAgICAgICAgbmNvbCA9IDEsCiAgICAgICAgICBhbGlnbiA9ICJodiIsIGF4aXMgPSAidGJsciIsCiAgICAgICAgICBsYWJlbHMgPSBjKE5BLAogICAgICAgICAgICAgICAgICAgICAiWWVhciAxIiwgTkEsIE5BLCBOQSwKICAgICAgICAgICAgICAgICAgICAgIlllYXIgMiIsIE5BLCBOQSwgTkEsCiAgICAgICAgICAgICAgICAgICAgICJZZWFyIDMiLCBOQSwgTkEsIE5BLAogICAgICAgICAgICAgICAgICAgICAiWWVhciA0IiwgTkEsIE5BKSwKICAgICAgICAgIHJlbF9oZWlnaHRzID0gYyguMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgMSwgLS4yLCAuNzUsIC4xLAogICAgICAgICAgICAgICAgICAgICAgICAgIDEsIC0uMiwgLjc1LCAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAxLCAtLjIsIC43NSwgLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgMSwgLS4yLCAuNzUpLAogICAgICAgICAgaGp1c3QgPSAtMC4xLAogICAgICAgICAgdmp1c3QgPSAtMC4xLAogICAgICAgICAgc2NhbGUgPSAuOTUpCgpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jb21iaV9hbHRfZW5nbGlzaC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDExKQpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jb21iaV9hbHRfZW5nbGlzaC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDExKQpgYGAKCiMgU2Vzc2lvbiBpbmZvCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK